首页
/ Flutter 引擎 AOT 模式嵌入实战:为第三方 Embedder 构建、编译与打包 AOT 快照

Flutter 引擎 AOT 模式嵌入实战:为第三方 Embedder 构建、编译与打包 AOT 快照

2026-09-06 15:12:07作者:何将鹤

本文面向使用 稳定 C 嵌入 API 的第三方 Embedder 开发者,完整讲解如何让 Flutter 应用脱离 JIT、以 AOT(Ahead-Of-Time)模式运行:从用 GN 构建 AOT 引擎、编译目标架构专用的 gen_snapshot,到生成 kernel 快照与四个 AOT 二进制块、按内存权限约束打包这些块,并最终通过 FlutterProjectArgs 把它们交给 FlutterEngineRun。读完本文,你可以为自定义平台(非 Android/iOS 标准工具链覆盖的平台)完整产出一套可在目标设备上直接加载的 AOT 工件。

一、背景:第三方 Embedder 与 AOT 模式

Flutter 引擎默认以 JIT 模式打包——这种引擎里携带的 Dart VM 无法加载 AOT 快照。如果第三方 Embedder(即用 embedder.h 暴露的稳定 C API 把 Flutter 集成到自己平台上的开发者)想以 AOT 模式发布应用,必须自己完成三件事:

  1. 构建一个针对 AOT 模式配置的 Flutter 引擎;
  2. 为目标架构生成 AOT 指令(kernel 快照 → 四个 AOT 二进制块);
  3. 在运行时把二进制块按正确的内存权限映射进地址空间,并配置引擎以 AOT 方式启动。

这四个二进制块的职责在 Flutter-engine-operation-in-AOT-Mode.md 中有权威定义:VM 快照数据(isolate 间共享的 Dart 堆初始状态,应位于只读数据段)、VM 指令(所有 isolate 共享的 AOT 例程与 stub,必须位于可执行文本段)、Isolate 快照数据(含 isolate 特定信息的堆初始状态,只读)、Isolate 指令(该 isolate 实际执行的 AOT 代码,可执行)。数据快照只需 READ 权限,指令快照必须 READ + EXECUTE——这一约束直接决定了后文的打包方式。

二、构建 AOT 模式的 Flutter 引擎

默认情况下,面向 Embedder 打包的 Flutter 引擎假定以 JIT 模式运行;JIT 引擎携带的 VM 与 AOT 快照不兼容。构建 AOT 引擎的标准入口是引擎源码里的 gn 脚本:

./flutter/tools/gn --runtime-mode release <必要时指定自定义 target 的标志>
  • --runtime-mode release:复用 Flutter 的 "release" 模式策略来准备 AOT 引擎。这个策略对第三方 Embedder 而言是可选的,是否采用由 Embedder 作者自行决定。
  • <custom target flags>:这里正是指定自定义 target、sysroot 和工具链等标志的位置。

注意:文中提到的 gn 标志与其他非运行时/配置类选项可以自由组合,例如 --no-lto--unoptimized 等。这些选项在开发调试阶段特别有用,官方明确鼓励使用。

该调用会产出一个针对宿主架构配置好 AOT 模式的 Flutter 引擎。tools/gn 就是引擎源码树中这个构建入口脚本,第三方 Embedder 通常从引擎源码根目录发起该命令。

三、构建目标架构专用的 gen_snapshot

把 Dart 代码转换成特定架构 AOT 指令的二进制叫做 gen_snapshot。一次成功的 gen_snapshot 调用应当产出前文所述的四个二进制块(VM 堆/指令快照 + Isolate 堆/指令快照)。

3.1 用 --version 验证目标架构

一个特定的 gen_snapshot 只能为一种目标架构生成 AOT 指令。用 --version 标志检查它在为什么架构工作:

$ gen_snapshot --version
Dart VM version: 2.1.1-dev.2.0.flutter-ac1bf656c4 (Thu Jan 17 16:55:19 2019 +0000) on "macos_x64"

末尾字符串表示 宿主/目标 对。上例中宿主是 macos、目标是 x64。如果作者在一台 macOS 机器上,一个为 aarch64 生成指令的 gen_snapshot 会显示类似 "macos_simarm64"

最简单的做法是选择一个架构最接近 Embedder 目标架构的受支持 Flutter target。例如目标为 armv7 时:

./flutter/tools/gn --android --runtime-mode release

面向 aarch64 的 AOT gen_snapshot 另一个常用调用是:

./flutter/tools/gn --android --runtime-mode release --android-cpu arm64

3.2 架构之外的隐蔽约束:调用约定与对齐

重要gen_snapshot 生成的 AOT 指令与目标之间,除了目标架构一致外,调用约定(calling convention)和对齐(alignment)也必须一致。复用 Flutter 的构建目标可以保证这些细节都被处理,但匹配 AOT 指令与目标的责任仍在 Embedder 一方。例如:--android --android-cpu arm64 配置的 gen_snapshot 生成的指令,并不完全兼容 iOS 上的 aarch64(尽管目标架构相同)。快照的架构/ABI 与设备架构/ABI 不匹配会在快照加载时被检测到并明确报错。

这一点在 Flutter-engine-operation-in-AOT-Mode.md 的 Notes 一节也有佐证:gen_snapshot 只能为特定架构生成指令(armv7aarch64i386x86-64 各需一个变体);四个工件通常必须同时由同一个 gen_snapshot 变体生成,不能把不同变体产生的指令与数据快照混用;从源码结构看,其根本原因是数据快照(堆内容)与指令的编码强依赖于同一编译器的假设。

四、构建 AOT 快照

生成目标架构的 AOT 指令分两步:先生成与目标架构无关的 kernel 快照(约等于 AST),再把它交给 gen_snapshot 生成四个 AOT 块(约等于机器码)。

4.1 便捷方式:复用 flutter build aot 工具链

iOS 和 Android 的 AOT 模式在构建产物时都要做这一步。如果 Embedder 目标与它们相近,可以直接复用 Flutter 工具链支持的工作流。例如为 Android 类目标生成 aarch64 的四个 AOT 块:

flutter --local-engine <local_engine_configuration> \
        --local-engine-host <local_host_engine_configuration> \
        build aot --target-platform android-arm64 --release
  • --local-engine / --local-engine-host:技术上可选,但强烈建议指定。不指定时工具会选用已发布的 Flutter 引擎版本,它可能与你用来准备 gen_snapshot 的引擎存在细微版本差异,锁定同一版本更安全。详见 Debugging-the-engine.md 中"使用本地引擎运行"一节。

调用结果是在 build/aot 目录生成以下四个二进制块:

文件名 含义 运行时内存权限
vm_snapshot_data VM 快照数据 只读
vm_snapshot_instr VM 快照指令 读-执行
isolate_snapshot_data Isolate 快照数据 只读
isolate_snapshot_instr Isolate 快照指令 读-执行

4.2 手动方式:先 kernel,再 AOT

若需要直接控制四个块的生成,就必须手动走完整流程。具体标志虽然晦涩但可读性强;必要时可以给 flutter build aot-v 标志,dump 出 Flutter 实际使用的精确标志,再按目标架构修改。

生成 kernel 快照

以下调用会在构建目录生成 kernel_snapshot.dill(执行前先在项目里运行 flutter packages get 拉取全部包依赖):

$FLUTTER_ENGINE_OUT_DIR/dart                                  \
    $FLUTTER_ENGINE_OUT_DIR/frontend_server.dart.snapshot     \
    --sdk-root $FLUTTER_ENGINE_OUT_DIR/flutter_patched_sdk/   \
    --strong                                                  \
    --target=flutter                                          \
    --aot                                                     \
    --tfa                                                     \
    -Ddart.vm.product=true                                    \
    --packages .packages                                      \
    --output-dill build/kernel_snapshot.dill                  \
    package:flutter_gallery/main.dart

关键参数解读:

  • --sdk-root .../flutter_patched_sdk/:使用打补丁后的 Flutter SDK(含 dart:ui 等 Flutter 扩展库);
  • --target=flutter:以 Flutter 为目标而非原生 Dart;
  • --aot + -Ddart.vm.product=true:按 AOT 产品模式编译(强模式、产品开关);
  • --tfa:启用树形流分析(tree-flow analysis);
  • --output-dill build/kernel_snapshot.dill:输出 kernel(.dill)文件;
  • 最后一个参数是应用入口(示例用 flutter_gallery,实际换成你的入口文件)。

生成 AOT 快照

得到 kernel_snapshot.dill 后,用如下参数调用 gen_snapshot 产出四个块:

$FLUTTER_ENGINE_OUT_DIR/gen_snapshot                              \
    --causal_async_stacks                                         \
    --packages=.packages                                          \
    --deterministic                                               \
    --snapshot_kind=app-aot-blobs                                 \
    --vm_snapshot_data=build/vm_snapshot_data                     \
    --isolate_snapshot_data=build/isolate_snapshot_data           \
    --vm_snapshot_instructions=build/vm_snapshot_instr            \
    --isolate_snapshot_instructions=build/isolate_snapshot_instr  \
    --no-sim-use-hardfp                                           \
    --no-use-integer-division                                     \
    build/kernel_snapshot.dill
  • --snapshot_kind=app-aot-blobs:声明要生成应用 AOT 的四个二进制块形式;
  • 四个 --*_snapshot_* 参数分别指定四个输出块的路径;
  • --deterministic:保证输出确定性;--causal_async_stacks:启用因果异步栈;
  • 两个 --no-* 标志并非所有目标都需要,可以按需跳过。拿不准时的标准做法:选择最相似的目标后运行 flutter build aot -v,查看 Flutter 使用的标志再作修改。

五、打包 AOT 块

四个 AOT 块必须随应用一起分发,FlutterEngineRun 调用离不开它们。如何打包与分发由 Embedder 自行决定。运行时这些块要映射进地址空间,并遵守以下权限约束:

  • vm_snapshot_data:只读(Read-Only);
  • vm_snapshot_instr:读-执行(Read-Execute);
  • isolate_snapshot_data:只读(Read-Only);
  • isolate_snapshot_instr:读-执行(Read-Execute)。

映射的生命周期由 Embedder 负责:只要 FlutterEngine 在运行并保持存活,这些映射就必须持续有效,不能提前解除。

作为对照,官方平台是这样做的(见 Flutter-engine-operation-in-AOT-Mode.md):iOS 因为引擎无法在运行时把页标记为可执行,把四个块编译进 App.framework 这样的动态库中,通过 kDartVmSnapshotData 等符号引用;Android 则把四个块直接打进 APK,由 libflutter.so 自行映射并保证可执行。第三方 Embedder 若希望走"单动态库"路线,也可以把快照打包成一个 ELF 动态库交给引擎(见下一节的现代 API)。

六、配置引擎进入 AOT 模式

在传给 FlutterEngineRunFlutterProjectArgs 结构体中,提供以下八个选项:

  • vm_snapshot_data:只读 VM 快照映射的指针;
  • vm_snapshot_data_size:VM 快照映射的大小;
  • vm_snapshot_instructions:读-执行 VM 指令映射的指针;
  • vm_snapshot_instructions_size:VM 指令映射的大小;
  • isolate_snapshot_data:只读 isolate 快照映射的指针;
  • isolate_snapshot_data_size:isolate 快照映射的大小;
  • isolate_snapshot_instructions:读-执行 isolate 指令映射的指针;
  • isolate_snapshot_instructions_size:isolate 指令映射的大小。

至此 Flutter 引擎即运行在 AOT 模式。

在源码中可以逐项核对这些字段的语义。embedder.h 中的注释与文档一一对应:

  • vm_snapshot_data(第 2586 行):"This buffer must be mapped in as read-only"——必须只读映射;
  • vm_snapshot_instructions(第 2594 行):"must be mapped in as read-execute"——必须读-执行映射;
  • isolate_snapshot_data / isolate_snapshot_instructions(第 2602、2610 行):同样分别要求只读与读-执行;
  • 四个 *_size 字段注明:如果对应指针是一个符号引用(如从动态库 dlopen 得到的符号地址),可以传 0——引擎会自行解析块的大小。

此外 embedder.h 还导出 FlutterEngineRunsAOTCompiledDartCode(void),供 Embedder 查询当前引擎是否运行在 AOT 编译的 Dart 代码之上。

补充:ELF 数据源 API(FlutterEngineCreateAOTData

embedder.h 的当前定义看,除了直接传四个裸缓冲指针,稳定 API 还提供了一条更简洁的 AOT 数据入口:

/// AOT data source type.
typedef enum {
  kFlutterEngineAOTDataSourceTypeElfPath
} FlutterEngineAOTDataSourceType;

typedef struct {
  FlutterEngineAOTDataSourceType type;
  union {
    /// Absolute path to an ELF library file.
    const char* elf_path;
  };
} FlutterEngineAOTDataSource;

typedef struct _FlutterEngineAOTData* FlutterEngineAOTData;

配合 FlutterEngineCreateAOTData(source, &data_out) / FlutterEngineCollectAOTData(data) 使用:把四个块打成一个 ELF 动态库(可参照 iOS 的做法),Embedder 只需提供绝对路径。embedder.cc 中该函数的实现会检查当前确实处于 AOT 模式、加载 ELF 并从中取出 vm_isolate_data / vm_isolate_instrs 等符号,再通过 PopulateAOTSnapshotMappingCallbacksembedder.cc)把四个快照的映射回调注入引擎设置。同文件第 2095 行附近的逻辑也明确了互斥约束:*_snapshot_* 裸缓冲指针与 aot_data 二者只能提供其一,同时提供会返回 "Multiple AOT sources specified" 错误。若走 aot_data 路线,映射生命周期由引擎代管,Embedder 不再需要自己维持映射存活。

七、检查清单

  • 引擎是用 ./flutter/tools/gn --runtime-mode release(或等价的自定义 target/sysroot/工具链组合)构建的 AOT 引擎;
  • gen_snapshot --version 显示的目标架构、调用约定、对齐与设备完全一致(同架构不同 ABI 不算一致);
  • 四个块由同一个 gen_snapshot 变体一次性生成,未混用不同变体;
  • 打包后在目标设备上映射权限正确:两个 data 块只读、两个 instr 块读-执行;
  • 映射在 FlutterEngine 存活期间一直有效(或改用 ELF 数据源 API 交给引擎管理);
  • FlutterProjectArgs 的八个 vm_snapshot_* / isolate_snapshot_* 字段全部正确填充后调用 FlutterEngineRun,引擎即以 AOT 模式运行。

参考文档:Custom-Flutter-Engine-Embedding-in-AOT-Mode.mdFlutter-engine-operation-in-AOT-Mode.mdDebugging-the-engine.md;核心实现见 embedder.hembedder.cc

登录后查看全文
热门项目推荐
相关项目推荐