研发

exping 开发之 Flutter 优化篇
Kunlin·2022 年 8 月 1 日

使用 Flutter,花了大半年的时间开发了exping。上线后,当周登上了 Apple Store 推荐榜单。

App Store 本周推荐 exping 截图

感谢大家的厚爱。但同时也暴露出它的不足: 页面浏览出现卡顿,掉帧,有了 “Flutter 体验真差,比原生差得远” 等类似评价。

当初选择Flutter,就是看中它的跨平台优势,极大降低开发成本,(两个开发童鞋,前后端(打杂)全包~)。

现在上线了,深受大家喜爱,那就必须,撸起袖子,搞它!!!(避免饭碗不保)


APP首次运行出现各种卡顿

收到最多反馈,就是各种页面卡顿... 产品,测试,开发 全都疑惑: ~为啥我们没觉得卡😅...

经过跟用户大佬们的再三讨教(掰扯),原来是首次打开卡顿,重新进入又又正常了。原来是我们被“卡”习惯了🥶。

之所以会出现这类问题,套用官方解释:SkSL着色器编译卡顿

首次运行 App,进入页面时,需要先对页面所使用着色器进行编译,再渲染页面。那要解决这个问题,就是对页面的所用着色器进行预热(预编译)。

Flutter 官网给出了具体解决方案:
https://docs.flutter.dev/perf/shader

来自Flutter官网的对比图

操作步骤

  1. 使用 --cache-sksl 运行,用于捕获着色器 (直接命令行运行)
flutter run --profile --cache-sksl
  1. 运行后,对APP有卡顿的页面,都浏览一遍。元素比较多的页面,可以多打开几次,特别有复杂动画的页面。

  2. 打开页面后,按下大写的 M (注意是大写~),会在项目目录生成 flutter_01.sksl.json,这个文件记录捕获到的着色器。

  3. 修改运行指令,将着色器文件打包进 APP,告诉 APP 那些着色器需要预热,那APP在首次运行时,就会对指定着色器进行编译后,再进入APP。

# 运行时指定
flutter run --profile --bundle-sksl-path flutter_01.sksl.json

# 打包时指定
flutter build apk --bundle-sksl-path flutter_01.sksl.json
  1. 卸载原来APP,安装打开新APP,眼前一亮,马上发给产品童鞋,立马收获一排大拇指👍🏻

此方案带来了极大的优化体验,但是也有弊端:

  1. 对APP体积大小有一点影响,毕竟多了一个json文件打包进去。但大小能接受。1MB 左右。

  2. 影响首次打开APP的速度。如果页面全覆盖,打开APP会有3-5秒的延迟。但与每个页面都给用户带来卡顿的体验,这性价比还是比较高。


部分页面进入时卡顿

完成上面优化后,部分页面在访问时,仍会有卡顿现象。排查着色器问题后,从自身代码上进行排查。

由于是进入页面瞬间卡顿,结合 DevTools 分析,注意到部分卡顿页面,有一个共性是: 在 initState 中,进行接口网络请求或者数据组装后,通知页面刷新。 如果这里的逻辑比较复杂或者频繁刷新Widget,就会出现严重的卡帧。由于Dart是单线程,就算是异步操作,当操作过于复杂耗时,仍会造成页面卡顿。(60hz下,要求不超过16ms)

解决方案其实也比较简单,避开在 initState 直接进行复杂操作:

/// 在下一帧再进行
WidgetsBinding.instance!.addObserver((_){
	// ... 具体操作
});

对相关页面调整后,进入页面时,卡帧问题已经明显得到改善。

注意左边顶部进入瞬间卡顿~

继续加把劲,让页面看起来丝滑点: 数据请求完成后,loading页面转场过于死板,一闪而过,容易造成卡顿假象.. 那就加个转场透明渐变:

AnimatedSwitcher(
	duration: const Duration(milliseconds: 200),
	child: child,
	transitionBuilder: (Widget child,Animation<double> animation) {
	  // 透明渐变
	  return FadeTransition(opacity: animation,child: child);
});

调整前后转场对比,(为了对比明显些,gif 速度放慢了0.8)


相册选择器优化

某个小伙伴反馈,我要选择照片,但相册一直加载不出来!!!

A: 你手机相册中有多少张照片?

B: 10万张左右吧

🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️🧎‍♂️

请收下我的膝盖... 立马抓来测试童鞋,去给测试机搞10万张图片,复现一下问题!

目前APP对图片的操作,使用插件: photo_manager

photo_manager | Flutter Package

相册选择器则是参考 wechat_assets_picker 的部分逻辑,重新封装成更符合APP需求的照片选择器:

https://github.com/iFREEGROUP/photo_picker

通过调试发现,当相册照片在5万张左右时,在调用PhotoManager.getAssetPathList 耗费时间大概10秒左右,如果照片在 iCloud,那耗费的时间更长。

PhotoManager.getAssetPathList 默认参数中 hasAll = true,onlyAll = false,通过分析源码发现,onlyAll 为 false 时,需要等待获取到所有相册路径,才会返回。

onlyAll 为 true 时,只获取 最近照片 的相册路径,耗时不超过 500ms。

按照这思路,修改下代码:

// 优先取出 "全部",加速照片显示
pathList = await PhotoManager.getAssetPathList(
  hasAll: true,
  onlyAll: true,
  type: config.requestType,
  filterOption: options,
);

if (!config.onlyAll) {
  // 再去异步获取其他文件夹
  PhotoManager.getAssetPathList(
    hasAll: false,
    onlyAll: false,
    type: config.requestType,
    filterOption: options,
  ).then((value) {
    config.getPhotoSortPathDelegate.sort(value);
    _allPathList.addAll(value);
    _cacheFirstThumbFromPathEntity(_allPathList);
  });
}

打开相册,优先获取最近照片显示出来,再去获取其他相册。

虽然无法加快 获取其他相册 的速度,但能快速呈现出照片给用户选择,减少等待时间,操作体验上提升了一大步。而异步去获取其他相册,则是无感知的。

优化完相册打开速度,需要再看看照片列表卡顿问题.

从查看 DevTools 发现,当页面同一帧加载 40 张(4*10) 时,出现红色警告:
enter image description here

那就避免一帧同时渲染太多图片,这里使用社区大佬的分帧插件:

keframe | Flutter Package

经过修改,从 DevTools 上可以看到,加载速度提升明显:

enter image description here

相册选择器的优化工作基本就这两项。而分帧加载的方案,也同时应用在元素多且复杂的页面上,有效降低卡帧现象。


除了前面提到的三点优化工作,同时也对部分不规范的或者有优化空间的代码逻辑进行修改调整,比如 封装弹框,重写emoji评分选择器,对地图Maker刷新显示规则进行调整 等等..

而针对iPhone高刷问题,目前Flutter master分支已经支持,可以通过调整Flutter版本分支,体验下高刷效果。我们也等Flutter更新稳定版本后,第一时间更新APP。

https://github.com/flutter/flutter/issues/90675


目前 exping 1.0.2 版本已发布。欢迎下载体验。https://exping.world