Flutter包体积之数据区域压缩分析与实践
Flutter版本为: 1.9.1
利用battery的example作为实验
battery的example工程在经过数据区域压缩后,App动态库由10.5MB减小到了7.9MB。当然所写的代码越多能减少的体积也会变多。和其它移除某些功能模块的方案相比,该方案我认为是收益最大的。
基础知识介绍
产物
Flutter.framework:编译过程中直接从Flutter环境中拷贝而来
- Flutter:Flutter引擎,Mach-O格式的动态链接库
App.framework: 编译工程时生成
- App:AOT Snapshot数据,由我们的Dart代码编译而成,Mach-O格式的动态链接库
这两个动态链接库都会在应用启动时,因为被最外层的Runner所使用而被加载进内存,可以通过otool查看Runner和它们的联系
Dart运行方式
Dart VM有三种运行方式
- 直接JIT运行源码或者Kernel binary (app.dill)
- 运行生成的JITSnapshot,和第一种方式相比利用快照减少了JIT预热时间
- 运行生成的AOTSnapshot,直接运行编译期编译好的机器码
iOS采用的是AOTSnapshot的方式,虽然JITSnapshot因为运行时会进行一些优化,当达到完全预热时,性能能达到最高。但是需要在引擎中引入即时编译器增加引擎体积。
Dart运行AOTSnapshot
Dart 虚拟机将已存在内存中的isolate的堆(驻留在堆上的对象图)序列化成二进制的快照文件,当在设备上再次启动虚拟机的时候可以从快照中快速重建形同的isolate状态。实质上由图可知是一个序列化和一个反序列化的过程。
Dart 虚拟机的快照和其它快照有些不同,是包含机器码的,当这块机器码是不需要反序列化的,因为放在代码区,映射到内存的时候可以直接成为堆的一部分。
App这里面只包含了4个符号
- _kDartIsolateSnapshotData
- _kDartIsolateSnapshotInstructions
- _kDartVmSnapshotData
- _kDartVmSnapshotInstructions
R 表示该符号位于只读数据区
T 表示该符号位于代码区
分析与实践
写入时压缩
Dart源码编译成App.framework的流程如下
bin/cache/artifacts/engine/ios-release/gen_snapshot_armv7 --causal_async_stacks --deterministic --snapshot_kind=app-aot-assembly --assembly=build/aot/armv7/snapshot_assembly.S --no-sim-use-hardfp --no-use-integer-division build/aot/app.dill
读取时解压
SearchMapping最后也是调用dlopen与dlsym去获取符号内容,所以在实现读取解压时,只需要在ResolveVMData和ResolveIsolateData中的合适的位置做解压缩的操作,并利用解压数据替换解压前的数据即可。
总结
更新的flutter版本还未尝试,应该变化也不大,这个方案也可以继续用。
参考
Introduction to Dart VM: mrale.ph/dartvm/
Flutter机器码生成gen_snapshot:gityuan.com/2019/09/21/…
转载自:https://juejin.cn/post/6844904131673456653