Flutter移动应用快速构建实践——状态管理、国际化、数据持久化、性能优化(二)
继续上一篇:极简诗词开发背后:Flutter移动应用快速构建实践——状态管理、国际化、数据持久化、性能优化(一)
前文中说到了项目结构、状态管理和国际化的方案,本文继续聊聊数据持久化和粗略的性能优化。
数据持久化
目前的话,flutter用的比较多比较成熟的数据持久化就是shared_preferences
和SQFLite
,一个是ini配置文件,一个是SQLite数据库,小应用我比较倾向只用前者,比较方便,Sqflite能用,但是flutter至今没有很好用的ORM框架,用起来需要写很多SQL语句就很难受了。
所以下面介绍的是shared_preferences
的简单用法,详细见官网:https://pub.flutter-io.cn/packages/shared_preferences
添加依赖:
dependencies:
shared_preferences: ^0.5.6
这里的官网的简单例子:
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
单纯抄一下官网的例子当然是不够的,我想说的数据持久化是这个,本项目用到的方法。
还记得上一篇写到的json model
模块吗,它提供的对象序列化和反序列化在持久化中很好用,在保存数据的时候,将model类转换给json数据存在SharedPreferences
配置中,例如:
/// 持久化Profile信息
static saveProfile() => _prefs.setString("profile", jsonEncode(profile.toJson()));
当App启动时,从SharedPreferences
中读出json数据,使用model类的json构造函数,创建(反序列化)为model对象,例子如下:
_prefs = await SharedPreferences.getInstance();
var _profile = _prefs.getString("profile");
if (_profile != null) {
try {
profile = Profile.fromJson(jsonDecode(_profile));
// 防止读取出来是null,用于从版本已有profile数据的升级,如果是全新安装的没有这个问题
profile.darkMode = profile.darkMode ?? false;
} catch (e) {
print(e);
}
}
这样就完成了数据持久化的工作,非常简便。
相关配置
接下来先讲一点有关项目配置的,无论是flutter开发还是Android开发,那都是谷歌的技术,谷歌嘛你懂的,服务器都是被屏蔽的,而且用到的Gradle那些maven源也大多是访问不了的,所以每次编译成Android Apk的时候都很慢,我们需要设置一下国内源。
修改 android/build.gradle
,把里面默认的google()
和jcenter()
改成下面的地址就好了。
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
}
还要修改 android/gradle/wrapper/gradle-wrapper.properties
文件。
#Wed Dec 11 15:10:31 HKT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
把最后一行改成国内的服务器,如:distributionUrl=https\://downloads.gradle-dn.com/distributions/gradle-5.1.1-all.zip
,改域名就可以了。
还有一个pub.dev
,dart语言的包发布网站,国内访问不了,但我们有国内镜像:https://pub.flutter-io.cn/
感谢国内开源社区的大佬们的努力!
还有关于迁移到AndroidX的,也是一个坑来的,可以参考:Flutter开发:迁移Flutter应用到AndroidX
Flutter的“多线程”
接下来聊聊有关性能优化的实践,小标题的多线程我加了双引号,dart语言是单线程的,和js差不多,在flutter里面,UI和其他操作都是放在同一个线程执行的,如果主线程里面有耗时操作的话,界面就卡住了,flutter默认是60帧显示,所以如果一个操作不能在1/60秒内完成,就会影响界面刷新,产生明显的卡顿感。
那到底怎么解决啊?dart提出了一个叫Isolate的东西,类似线程,但是和主线程之间又不能共享内存,如果要共享数据,必须通过复制内存的方式传参和获取返回值,使用起来和多线程相比还是有很多限制的。
关于Isolate的详情我就不介绍那么多了,了解详情请查看官方文档和大佬的博客。这里记录一下我的实践。
前面说到主线程和isolate之间的切换开销很大,所以为了使用多线程又需要提升性能的话,就要减少isolate的创建,所以我们可以用“线程池”做负载均衡,在代码定义一个LoadBalancer,用来存我们的isolate。
Future<LoadBalancer> loadBalancer = LoadBalancer.create(4, IsolateRunner.spawn);
使用的时候通过loadbalancer创建可复用的isolate:
final lb = await loadBalancer;
lb.run(Isolate方法, 参数);
isolate方法是一个静态方法,接收一个参数,因为只能一个参数,所以要传多个参数的时候就需要自己定协议了,可以自己写一个类,也可以传dict或者list,我比较习惯传dict,不要单独再定义一个类。执行完的结果通过返回值返回,我一般也是返回一个dict(map)。
注意:isolate不能访问主线程的内存,在isolate内访问其他地方的变量都是无效的,请使用传参+获取返回值的方式交换数据。
并且现在不能使用print函数打印日志,也不能通过channel访问Android的logcat输出日志,据说是flutter的bug,唯一的方法是使用官方devtools里的日志工具,参考:https://flutter.dev/docs/testing/code-debugging
未完待续
不知不觉又写了很多了,还有剩下一些零碎的内容,以及更多性能优化和App体积优化的内容正在研究和总结中,下篇文章再继续更新~
欢迎交流
交流问题请在微信公众号后台留言,每一条信息我都会回复哈~ - 微信公众号:画星星高手 - 打代码直播间:https://live.bilibili.com/11883038 - 知乎:https://www.zhihu.com/people/dealiaxy - 简书:https://www.jianshu.com/u/965b95853b9f