上一篇,咱们分享了 Flutter 在两轮的利用推广,本次分享的主题是 Flutter 在两轮的降级之路,次要分为两局部。一是咱们在 Flutter 落地之后,因为业务的倒退,导致咱们须要对 Flutter 进行降级。二是降级之后咱们遇到了一些问题,这里列举了一个比拟典型的案例——FlutterEngine 的自定义。
背景介绍
降级起因
两轮从 2019 年来开始应用 flutter,算是利用比拟早的团队。随后 flutter 降级了 2.x 版本,这个版本有个比拟重要的更新就是空平安(nullsafety),因为在 1.x 的版本,如果一个对象为 null 之后,再去调用它的任何办法,都会抛异样,在页面上的体现就是页面假死,也不会再刷新了。所以就须要研发人员关注下本人应用变量时是否曾经为空了,2.0 会强加这个查看,在编译阶段让咱们确定好次变量是否可空以及是否懒加载,这样在咱们应用变量时,就会有零碎揭示咱们,相当于帮咱们做了这个工作,缩小出错率。其实很多其余语言也都有这样的检测机制。再加上一些性能的提优,于是有了降级的诉求。
Flutter 版本敲定
这里比照了一下 1.x 版本和 2.x 版本的次要区别。2.x 版本优化了利用启动的提早,调用 Dart VM 的 GC 策略也做了一些改良,低端 Android 设施的初始帧出线间隔时间最多缩小了约 300ms。
此外,空对象音讯是咱们在开发中最常见的 exception,所以空平安是很有必要的。pub.dev 曾经公布了超过 1000 个空平安 package,包含来自 Dart、Flutter、Firebase 和 Material 团队公布的数百个 package。
降级施行
基础设施
基础设施降级次要波及到两方面,打包机 sdk 降级和集体研发侧版本管理工具。因为 SDK 降级在 2.8.1 版本删除了一些被弃用的 Api,会造成咱们编译报错。可参考以下批改:
- 切换本地环境为 2.8.1 版本
- 运行工程查看报错
- 批改对应报错库的 api
- 本地运行胜利后,挪动端 CI/CD 零碎发库提测
- 测试同学全量回归,灰度
降级节奏
整体的降级节奏包含:根底降级启动、各个业务线研发启动、业务线 null_safety 革新 1 - n 次、全量回归、灰度公布和异样观测。
本次 2.0 降级工作量根本都在 null_safety 语法革新上,因为工作量大波及范围广所以无奈在短期内实现语法革新。所以本次革新将按照业务性能划分,尽量收拢每次语法革新带来的影响范畴,从而引起测试侧屡次全量测试的人力节约。
Flutter 空平安
健全的空平安已在 Dart 2.12 和 Flutter 2.0 及更高版本中可用。Dart 3 及当前的版本将只反对健全的空平安。
有了空平安,上面代码中所有的变量都是非空的:
依赖项查看
应用官网工具进行类型推断,最好确保依赖的所有包都曾经降级为 null safe 版本。所以在革新时应该从底层向上逐渐降级。
在控制台执行如下命令:dart pub outdated —mode=null-safety
能够查看到以后我的项目工程依赖包是否是 null safe。
对于其中不符合要求的外部库,须要手动降级后打包,生成 null safe 版本,倡议版本号比之前最新版减少一个大版本;对于三方库能够在三方库官方站点或者中文站点查找合乎 null safe 的最新版本号,并批改我的项目工程的 yaml 文件。
全副依赖批改实现后,执行命令拉取最新依赖:
flutter pub get
flutter pub upgrade
操作实现之后能够应用第一步的命令查看是否依赖都校对完了。
如果提醒所有依赖都申明反对 null safe,或者冀望跳过依赖查看,就能够进行后续操作。
执行胜利之后,会生成 url,点击可在浏览器中应用迁徙工具:
左侧是须要迁徙的文件目录,官网工具会对选中文件进行类型推断并主动增加如下标记:
- ! (类型不可为空)
- ? (类型可为空)
- late (初始化延时)
- late final (一次性初始化延时)
- required (参数标记)
面板两头能够看到具体改变,右侧能够看到以后文件改变的起因。
对于不须要迁徙或者大型文件须要分步迁徙的场景,能够只勾选须要迁徙的文件目录,官网迁徙工具会主动为不须要迁徙的文件顶层增加 null unsafe Dart 的版本正文。
引入反对 null safe 的第三方库
在更改本地代码时,个别都须要先降级依赖的第三方库,然而在官网第三方库下载地址 https://pub.flutter-io.cn 或者中文站点 https://pub.flutter-io.cn 中查问到依赖库的最新版本。
常见谬误及批改形式
- 谬误 1:The non-nullable variable ‘preferences’ must be initialized.
起因:非空的动态变量或者顶层变量在申明时没有初始化,诊断器就会报此谬误。因为 flutter 对于没有初始化的变量会主动赋值为 null,而该变量并没有申明可空。
解决办法:
- 谬误 2:The method ‘*‘ can’t be unconditionally invoked because the receiver can be ‘null’.
起因:可能为空的变量间接应用 ’.’ 调用办法,诊断器就会报此谬误。
解决办法:
- 谬误 3:Map<int, list> 简单类型批改
解决办法:
- 谬误 4:The default ‘List’ constructor isn’t available when null safety is enabled.
解决办法:
FlutterEngine 的 Crash 问题
在上线之后,安稳运行一段时间,咱们也继续察看监控数据,发现在 native 有大量新类型 crash。
[FlutterEngine destroyContext] 相干的 crash
解体起因是在用户敞开利用的时候会告诉到 flutter 引擎进行重置操作 (调用办法看【FlutterEngine destroyContext】)。重置的时候因为官网问题导致 engine 被屡次重置销毁。在第二次的重置的时候(OnPlatformViewSetSemanticsEnabled 这个办法里边获取引擎指针的时候解体了)
发现问题后,咱们去翻看了官网 issue,发现官网曾经修复该问题,不过是在后续版本上修复的,联合咱们本身状况(此 crash 产生是在后盾)咱们外部研究了几种解决方案:
- 解决方案 1
官网曾经修复了该问题,并曾经合并代码到 master。
修复的 pull request:https://github.com/flutter/engine/pull/30835/commits
bufix 的形容:
https://github.com/flutter/flutter/issues/95844
合并到引擎 commit 记录:https://github.com/flutter/engine/commit/fb3ee7f2b5e6e537a2a83c9fe2cf733cd9c6ec06
在高版本,2.10.2(2.10.3+) 以上此类问题已修复。
2.8.1Cherrypicks:
https://github.com/flutter/engine/pull/30355
但目前无奈间接降级到 flutter 版本到 2.10.2 以上,临时的解决办法是本地 cherry pick 代码,编译打包 flutter engine,替换 2.8.1 的 flutter engine。
危险最小然而须要评估改 pull request 的 cherry pick 老本,须要根底技术支持打包计划文档,须要摸索引擎打包交融到 flutter 批改打包,再到 flux 打 flutter 包流程交融。
- 解决方案 2
flutter 引擎从 2.8.1 降级到 2.10.4
- 解决方案 3
能够 hook destroyContext 在判断利用状态是否调用 destroyContext,但这个有肯定危险可能会影响其余的逻辑。
- 解决方案 4
梳理引擎切换的场景,通过业务上防止减小 crash 产生。
最终咱们抉择了激进的计划肯定制 FlutterEngine。
环境筹备
git
Python
Xcode
depot_tools 创立目录 /Users/xx/Desktop/flutter-engine
depot_tools 装置(须要全局 VPN 否则很慢)
cd /Users/xx/Desktop/flutter-engine
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
配置环境 (~/.zshrc 或者~/.bashrc)
export PATH=/Users/xx/Desktop/flutter-engine/depot_tools:$PATH
这里有个配置文件须要留神下:
gclient
配置在 /Users/xxx/Desktop/flutter-engine 目录 engine 文件夹创立.gclient 文件
同步依赖
cd /Users/xxx/Desktop/flutter-engine/engine
gclient sync
编译产物
cd /Users/xxx/Desktop/flutter-engine/engine/src
# --simulator 就是模拟器
# --ios-cpu=arm 就是 armv7,不指定默认就是 arm64
flutter/tools/gn --ios —unoptimized
在 src/out/ios_debug_unopt 目录下,会生成一个 Xcode 我的项目,用于 debug 时候应用引擎源代码。
src/out/host_debug_unopt,我在 debug 引擎时候发现须要外面的 dart-sdk,所以这个也须要编译一下。如果没有批改 dart 层代码就不须要执行这一步。
执行 ninja,生成 framework 产物。
ninja -C out/ios_debug_unopt && ninja -C out/host_debug_unopt
编译过程很漫长,能够在 ninja 前面加上参数 -j 2,防止编译占用过多的电脑资源,影响你开发。
编译实现之后,就要创立最初的 Flutter.xcframework。
官网也有提供 python 脚本工具,src/flutter/sky/tools 目录下的:create_ios_framework.py 和 create_macos_gen_snapshots.py
创立 Flutter.xcframework:release 版本加 –dsym 参数会生成 dysm 符号表
cd ~/Desktop/flutter-engine/engine/src/flutter/sky/tools
# debug 版本 Flutter.xcframework
python create_ios_framework.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug_arm --simulator-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug_sim
# profile 版本 Flutter.xcframework
python create_ios_framework.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios-profile --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_profile --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_profile_arm --simulator-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug_sim
# release 版本 Flutter.xcframework
python create_ios_framework.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios-release --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_release --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_release_arm --simulator-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug_sim --dsym
# 每个版本都加上了模拟器的局部,便于应用方模拟器调试
创立 gen_snapshots
cd ~/Desktop/flutter-engine/engine/src/flutter/sky/tools
# debug 版本 gen_snapshot_arm64 和 gen_snapshot_armv7
python create_macos_gen_snapshots.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_debug_arm
# profile 版本 gen_snapshot_arm64 和 gen_snapshot_armv7
python create_macos_gen_snapshots.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios-profile --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_profile --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_profile_arm
# release 版本 gen_snapshot_arm64 和 gen_snapshot_armv7
python create_macos_gen_snapshots.py --dst ~/Desktop/flutter-engine/engine/src/out/vd/ios-release --arm64-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_release --armv7-out-dir ~/Desktop/flutter-engine/engine/src/out/ios_release_arm
# 模拟器是 JIT 的,不须要 gen_snapshot
将来瞻望
俗话说,工欲善其事必先利其器。目前两轮 Flutter 降级后安稳运行,对业务迭代疾速倒退保驾护航。将来,两轮在大前端甚至能够持续摸索 flutter 的新特效,如 flutter-web,为大前端在小程序,taro,h5,app 的大交融夯实根底。
(本文作者:叶旸)