总结一下之前开发的Flutter遇到的问题。大概是19年寒假的时候开始了解Flutter,那时候是Flutter正式版本1.0.0刚发布的时候。大二以来一直在学习iOS开发,但是移动端开发我的亲身体验就是逐渐没落了,在我们学校我这一届也只有我一个人在学iOS开发,Android开发的人也相较于前端也是挺少的。所以,当时感觉为了毕业能够找口饭吃,对混合开发燃起了兴趣。之前对Facebook公司的React Native也了解过,但是一直没有花时间去学的打算,还是感觉差了点意思。但是当我开始了解Flutter的时候,被它的整个设计理念所吸引了。所以,大三下学期的时候开始花时间学习起来,刚好大三下学期我也开始备战考研了,所以基本都是晚上在图书馆复习完回到租的地方洗完澡之后到入睡前的时间花时间来学习Flutter和Dart语言。断断续续学了一个月的时候,刚好我们有一门课是计算机综合开发,我打算用Flutter完成这门课的课设,与此同时,那段时间我经常和一个朋友聊天,于是萌生了开发一个IM的APP,因为她是Android我是iOS,所以正好用Flutter来完成开发。
学习Flutter最快的途径就是撸官方文档了,还有就是Flutter中国的一本开源书籍。还有GitHub上有很多很棒的关于Flutter项目,例如阿里拍卖前端团队的flutter-go有各种widget的用法示例,awesome-flutter里面有各种flutter的开源库集合。Flutter上手特别快,但是如果一直写iOS的人可能会感觉很不适应Flutter的UI写法。
Flutter开发遇到的问题
我开发的第一个Flutter应用名字叫Nora,这个取名来自日文意思是流浪。学习和开发期间遇到了许多问题特地记录一下。但是由于写这篇文章的时间与开发应用的时间相比已经过去了很久,可能有些细节很难叙述清楚了。Nora后端使用的是Node.js的Express框架和MongoDB数据库,IM部分接入的是极光IM。
设置应用的启动页面
这是App开发的常见需求,在Flutter中只需要按iOS和Android相应的部分设置就行了,iOS这边直接用Xcode打开修改LaunchScreen.storyboard这个文件,图片放在项目根目录下。
安卓这边则需要在android目录下面找到launch_background.xml这个xml文件,把不同dpi的启动图放入指定目录下然后修改xml即可。
Flutter自动登录问题
为了方便用户登录,我们通常需要给应用做自动登录处理,通常在iOS我们需要在AppDelegate这个类的实例方法里把自动登录的逻辑写进去就行了。但是在Flutter我想用同样的思路去写的时候发现了一个问题,就是无论是OC还是Swift都是多线程的,页面渲染放在主线程,其他的耗时操作放在子线程里,所以我们是可以一边渲染页面一边进行其他操作的。但是在Flutter中,Dart是单线程语言,由于异步机制的问题,在StatefulWidget中,build方法都是最开始执行的,我们重写initState方法之后,里面代码的执行也是在页面渲染之后的,因为这样所以不能在第一个页面出现前就取得是否能自动登录的结果。
需要增加闪屏页
为了能获取用户能否自动登录的结果,在启动页之后增加闪屏页,如果有广告页面需求的在这里就可以设置广告页面了与此同时获取自动登录的结果。
import 'package:nora_chat/utils/jmessage_util.dart';
import 'package:nora_chat/utils/sp_util.dart';
import 'package:nora_chat/event/event_bus.dart';
import 'package:nora_chat/utils/constants_util.dart';
import 'dart:async';
import 'package:flutter/material.dart';
class SplashPage extends StatefulWidget {
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
@override
void initState() {
super.initState();
_initTimer();
}
_initTimer() async {
try {
await SpUtil.getInstance();
JMUserInfo userInfo = await jmessage.getMyInfo();
//设置默认主题
if(SpUtil.getString(ConstantsUtil.SYSTEM_THEME) != null) {
eventBus.fire(UpdateTheme(message: SpUtil.getString(ConstantsUtil.SYSTEM_THEME)));
}
// 延时一秒
await Future.delayed(Duration(seconds: 1),(){
if (userInfo.username != null) {
Navigator.pushReplacementNamed(context, '/main_page');
} else {
Navigator.pushReplacementNamed(context, '/login_page');
}
});
} catch (error) {
Navigator.pushReplacementNamed(context, '/login_page');
}
}
@override
Widget build(BuildContext context) {
return Builder(builder: (context) {
return Container(
color: Colors.white,
child: Image(image: AssetImage('assets/images/splash.png'), fit: BoxFit.fill,),
);
});
}
}这里需要注意的是路由方法用pushReplacementNamed,这样用户就不会可以返回到闪屏页了。
全屏图片闪屏问题
设置了全屏图片之后发现了一个问题,启动图之后加载第一个页面就是闪屏页面的时候屏幕会闪一下再加载,因为我的启动图和闪屏页面是一样的图片所以体验效果很不好。当然这是在debug模式下启动应用,当我用release模式在安卓手机测试的时候基本上不会出现这个问题了,iOS也是用profile或者release也基本不会出现这个问题。个人感觉就是debug模式下由于dart采用的是JIT模式,所以执行效率会低很多,从而导致页面渲染不够快。我的iPhone6s真机测试用release模式也偶尔会出现闪屏,可能是现在来说我的手机性能太差了- -。
Flutter页面保活问题
在iOS中,我们最常见的应用结构就是主页面先是一个tabbarController,然后将它的childController设置为NavigationController进行页面的导航。Flutter提供了一个名为Scaffold的Widget类,它是一个StatefulWidget,是一个构建应用页面的脚手架,用它可以构建出常见的应用结构。
例如:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(_navIndex),
body: _buildBody(),
drawer: LeftDrawerPage(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _navIndex,
onTap: (index) {
setState(() {
_navIndex = index;
});
_pageController.jumpToPage(index);
},
unselectedItemColor: Theme.of(context).accentColor,
fixedColor: Theme.of(context).accentColor,
type: BottomNavigationBarType.shifting,
items: [
BottomNavigationBarItem(
icon: Icon( Icons.chat), title: Text('会话')
),
BottomNavigationBarItem(
icon: Icon(Icons.supervisor_account),title: Text('联系人'),
),
BottomNavigationBarItem(
icon: Icon(Icons.share),title: Text('心情'),
)
],
),
);
}页面是这样子的
页面不保活
当我点击底部的barItem的时候会发现前一个页面的dispose方法执行了,新页面的initState方法执行了,再点击会前一个页面前一个页面的initState方法执行了,所以说不做处理的话页面是不会保活的。我们想要实现iOS一样的效果还需要做一些处理,官方也想到了这个问题所以也提供了解决方案。首先我们的主页面需要遵守SingleTickerProviderStateMixin这个协议,然后Scaffold的body需要使用PageView这个Widget。
Widget _buildBody() {
return PageView(
controller: _pageController,
// 禁止手势滑动切换页面
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
ConversationPage(),
AddressBookPage(),
AblumPage(),
],
);
}接着每一个子页面需要遵守AutomaticKeepAliveClientMixin这个协议,然后重写
@override
bool get wantKeepAlive => true;最后需要注意的就是每一个子页面的build方法里面要先去执行super.build(context)这样就能够保证每个子页面只会加载一次。



哈哈哈,写的太好了https://www.lawjida.com/
兄弟写的非常好 https://www.cscnn.com/
看的我热血沸腾啊https://www.237fa.com/
看的我热血沸腾啊
想想你的文章写的特别好https://www.jiwenlaw.com/