Flutter移动应用快速构建实践——状态管理、国际化、数据持久化、性能优化(二)

继续上一篇:极简诗词开发背后:Flutter移动应用快速构建实践——状态管理、国际化、数据持久化、性能优化(一)

前文中说到了项目结构、状态管理和国际化的方案,本文继续聊聊数据持久化和粗略的性能优化。

数据持久化

目前的话,flutter用的比较多比较成熟的数据持久化就是shared_preferencesSQFLite,一个是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