上一篇,咱们分享了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的大交融夯实根底。
(本文作者:叶旸)
发表回复