[译]Flutter 的编译模式
原文
使用过 Flutter 构建 APP 的人可能有个疑惑,Flutter 的编译产物有时候是一个共享库,有时候是几个文件,比较难于理解,本文主要介绍 Flutter 中的编译模式。
编译模式分类
源代码需要编译才能运行,一般来讲编译模式分为 JIT 和 AOT 两大类
JIT
全称为 Just In Time(即时编译),比较典型的就是 V8 JS引擎,它能够即时的编译和运行 Javascript 代码。你只要输入 Javascript 的字符串源码,V8 就能编译和运行这段代码。通常来说,支持 JIT 的语言一般能够支持自省函数(eval),在运行时动态的执行源码。
所以 JIT 模式有个显然的优点,你可以直接将代码分发给用户,而不用考虑用的机器架构。有这个,就可以为用户提供丰富和动态的内容。
但缺点也是比较明显的,大量的字符串源码,将花费 JIT 编译器大量的时间和内存来编译和执行,会让用户感觉应用启动缓慢。
AOT
全称为 Ahead Of Time(事前编译), 典型的例子就是像 C/C++ 需要被编译成特殊的二进制,才可以通过进程加载和运行。
AOT 的优势就是速度快,通过事先编译好的二进制代码,加载和执行的速度都会非常快,在密集计算或者图形渲染的场景下能够获得比较好的用户体验。
但 AOT 也有一些缺点,编译源代码的时候,需要注意用户的设备架构。对于不同的构架需要生成不同的二进制代码,也就会增加应用需要下载的程序包大小。而且二进制代码需要获得执行权限,所以无法在权限比较严格的系统(比如iOS)中动态更新。
Dart 的编译模式
Flutter 使用 Dart 作为开发语言,自然和 Dart 的编译模式脱离不了关系,所以我们先来看一下 Dart 的编译模式。
Script: 最常见的 JIT 模式,就像 Node.js 那样,可以在命令行直接执行 Dart 源代码
Script Snapshot: 不同于 Script 模式,Script Snapshot 模式载入的是已经 token 化的 dart 源码,提前做了编译期间的 Lexer 步骤, 也是属于 JIT 模式
Application Snapshot: JIT 模式,这种模式来源于 dart vm直接载入源码后 dump 出数据。dart vm 通过这种数据启动会更快。不过这种模式是区分架构的,在 X64 下生成的无法给 IA_32 用
AOT: AOT 模式,dart 源代码会被编译成汇编文件,汇编再经过汇编器生成不同架构下的二进制代码
用表格总结一下:
Flutter 的编译模式
Flutter 程序完全用 Dart, 理论上 Flutter 的编译模式应该和 Dart 的一致,事实上因为 Android 和 iOS 生态的差异,Flutter 衍生了不同的编译模式。
Script: 和 Dart 的 Script 模式一样,但是没有开启使用
Script Snapshot: 也是和 Dart 的 Script Snapshot 模式一样,也没有开启使用
Kernel Snapshot: Dart 的 bytecode 模式,在某种程度上类似 JVM。在 Flutter 项目中也被叫做 Core Snapshot,它是和设备架构无关的
Core JIT: 一种编译后的二进制格式,程序的数据和指令被打包成特殊的二进制,在运行时加载。事实上Core JIT 也被叫做 AOT Blobs, 是 AOT 的一种
AOT Assembly: 和 Dart 的 AOT 模式一样
所以在 Flutter 里会更复杂一点,我们结合 Flutter 的各个开发阶段来解读一下。
开发阶段
开发 Flutter APP 的时候,我们需要 HOT Reload 方便 UI 快速成型,同时也需要比较高的性能来进行视图渲染,所以 Flutter 在 Debug 下使用了 Kernel Snapshot 编译模式,会生成如下产物:
isolate_snapshot_data: 加速 isolate 启动的数据,和业务无关
vm_snapshot_data: 加速 Dart VM 启动的数据,和业务无关
kernel_blob.bin: 业务代码产物
发布阶段
在生产阶段,应用需要非常快的速度。所以 Flutter 使用了 AOT 编译模式,但是在不同的平台下,还是有些不一样的
在 iOS 平台由于 App Store 审核条例不允许动态下发可执行二进制代码,所以在 iOS 上,除了 Javascript,其他语言都使用 AOT 编译。
但是在 Android 平台上,Core JIT 和 AOT Assembly 都支持了,在 Core JIT 模式下会生成四个产物:isolate_snapshot_data/vm_snapshot_data/isolate_snapshot_instr/vm_snapshot_instr
vm_snapshot_instr 和 isolate_snapshot_instr 是 Dart VM 和 isolate 的指令,在载入后,直接将该块内存执行即可。
isolate_snapshot_data 和 vm_snapshot_data 是 Dart VM 和 isolate 的启动数据,主要是为了加速启动速度。
Flutter Engine 支持情况
Flutter Engine 包含了 Dart 的运行时,Flutter 应用的编译产物必须和 Engine 匹配才行,Engine 在不同的阶段提供了不同的支持
通过 gen_snapshot -h 也可以查看到,Engine 对编译模式的支持,事实上 Dart 源码就是经过 gen_snapshot 去处理的。
To create a core snapshot:
--snapshot_kind=core
--vm_snapshot_data=
--isolate_snapshot_data=
To create an AOT application snapshot as assembly suitable for compilation
as a static or dynamic library:
--snapshot_kind=app-aot-assembly
--assembly=
[--obfuscate]
[--save-obfuscation-map=]
To create an AOT application snapshot as an ELF shared library:
--snapshot_kind=app-aot-elf
--elf=
[--strip]
[--obfuscate]
[--save-obfuscation-map=]
AOT snapshots can be obfuscated: that is all identifiers will be renamed
during compilation. This mode is enabled with --obfuscate flag. Mapping
between original and obfuscated names can be serialized as a JSON array
using --save-obfuscation-map= option. See dartbug.com/30524
for implementation details and limitations of the obfuscation pass.
AOT snapshots 也可以使用 obfuscated 选项开启混淆, --save-obfuscation-map= 保存符号 mapping 文件。