Flutter 引擎磁盘占用追踪与调试:treemap 分析、devicelab 体积基准与 AOT 快照大小对比
2026/9/7 3:03:07 网站建设 项目流程

Flutter 引擎磁盘占用追踪与调试:treemap 分析、devicelab 体积基准与 AOT 快照大小对比

【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter

本指南围绕 Flutter 仓库中引擎产物(尤其是 Android 平台的libflutter.so、APK/IPA 安装包、icudtl.dat与 VM/isolate 快照)的磁盘占用追踪方法展开。读完本文后,你将掌握:如何通过 CI 产物查看引擎各组件大小的 treemap、如何在本地用binary_size_treemap.sh自行生成 treemap、devicelab 上持续追踪 APK/IPA 体积的基准指标含义,以及如何用gen_snapshotcompare_size.dart做 AOT 快照的逐方法字节级 diff 分析。

为什么需要追踪引擎的磁盘占用

Flutter 引擎的最终产物(libflutter.so、iOS 的Flutter.frameworkicudtl.dat、AOT 快照等)直接决定了用户下载包的大小。Flutter 仓库为此提供了一套“从粗到细”的工具链:

  1. treemap(树状图):把libflutter.so按符号/组件拆分成面积图,直观看到每个组件对二进制体积的贡献;
  2. devicelab 基准:每次提交都在 CI 上编译 Flutter Gallery 与最小 hello_world 应用,持续上报 APK/IPA 及其中关键产物的压缩/未压缩大小;
  3. AOT 快照大小对比:在改动 Dart 代码或引擎后,用gen_snapshot--print-instructions-sizes-to输出指令大小 JSON,再用compare_size.dart生成“哪个库、哪个方法变大/变小了多少字节”的表格。

这三层工具分别回答三个问题:引擎里什么占地方最近一次改动让安装包变大还是变小具体是哪些 Dart 方法让 AOT 代码变大

Treemap:查看libflutter.so的组件级大小分布

CI 自动生成的 treemap

在 CI 中,每笔提交都会为 release 版本的libflutter.so生成 treemap,图示其中各组件的相对大小。产物上传到对象存储,并从 CI 构建页面链接出来。在构建系统中定位某个 commit 的 treemap 的步骤是:

  1. 打开 flutter/flutter 仓库 main 分支的 commit 列表;
  2. 找到要评估的 commit,点击其绿色 checkbox 查看各项 check;
  3. 找到名为Linux linux_android_aot_engine的 check,点击details
  4. 点击View more details on flutter-dashboard进入 LUCI 构建页面;
  5. 展开名为launch builds的区域;
  6. 找到类似Linux Production Engine Drone for ci/android_release_arm64的被触发构建并点开;
  7. 在该构建的log links区域中找到并点击index.html链接,即可打开 treemap。

本地生成 treemap

不想依赖 CI 时,仓库内置的 binary_size_treemap.sh 可以在本地生成同样的 treemap:

flutter/ci/binary_size_treemap.sh <path/to/libflutter.so> <output_directory>

(注意flutter/ci是引擎源码目录内的路径,即本仓库中的 engine/src/flutter/ci。)

从脚本源码看其工作方式:

  • 它要求输入是libflutter.so一类的 ELF 动态库,输出目录即 treemap HTML 的存放位置;
  • 脚本会定位到引擎构建根目录(ENGINE_BUILDROOT),然后调用 Dart SDK 附带的二进制体积分析工具 run_binary_size_analysis.py,命令核心为:
    python3 "$RUN_BINARY_SIZE_ANALYSIS" --library "$INPUT_PATH" \ --destdir "$DEST_DIR" \ --addr2line-binary "$ADDR2LINE" \ --nm-binary "$NM" --jobs 1 --no-check-support
  • 它按主机系统(macOS 用darwin-x86_64、Linux 用linux-x86_64)选取 Android NDK 28.2.13676358 中的llvm-addr2linellvm-nm(见 binary_size_treemap.sh):nm用来枚举符号,addr2line用来把地址区间映射回源码/符号名,从而算出每个组件的体积贡献。因此该脚本需要在完整的引擎构建环境中运行(能取到对应版本的 NDK 工具链)。

treemap 的用途是“定位大头”:当某次提交导致libflutter.so明显变大时,先在 treemap 中看是哪一块区域(图形库、文本渲染、平台通道等)面积增长,再进入下一层工具做细粒度归因。

devicelab 基准:持续追踪 APK/IPA 与引擎产物大小

Flutter 仓库的 dev/devicelab 中有一组针对包体积的基准测试,随每笔提交运行,结果展示在构建仪表盘上。与引擎大小最相关的指标有:

指标类别指标名(<suite>__compile/<metric>形式)含义
Flutter Gallery 的 APK/IPA 总大小Android:flutter_gallery_android__compile/release_size_bytes
iOS:flutter_gallery_ios__compile/release_size_bytes
完整 Gallery 应用 release 安装包大小
最小 hello_world 应用 APK/IPA 大小Android:hello_world_android__compile/release_size_bytes
iOS:hello_world_ios__compile/release_size_bytes
剥离业务代码后的“纯 Flutter”基线体积
icudtl.dat大小压缩在 APK 内:hello_world_android__compile/icudtl_compressed_bytes
未压缩:hello_world_android__compile/icudtl_uncompressed_bytes
ICU 数据文件的体积
release 模式libflutter.so大小压缩在 APK 内:hello_world_android__compile/libflutter_compressed_bytes
未压缩:hello_world_android__compile/libflutter_uncompressed_bytes
引擎主体动态库的体积
VM 与 isolate 快照(数据 + 指令)压缩在 APK 内:hello_world_android__compile/snapshot_compressed_bytes
未压缩:hello_world_android__compile/snapshot_uncompressed_bytes
AOT 快照产物的体积

从源码结构看这些指标的产生位置:devicelab 的编译基准统一由 perf_tests.dart 驱动。其中_compileApp方法负责 release 构建并上报总大小(perf_tests.dart):

  • Android:执行flutter build apk --release --target-platform=android-arm --tree-shake-icons --split-debug-info=infos/,然后取build/app/outputs/flutter-apk/app-release.apk的文件大小作为release_size_bytes
  • iOS/macOS:执行flutter build ios|macos --release --tree-shake-icons --split-debug-info=infos/,找到 app bundle 后执行tar -zcf build/app.tar.gz <app>,以该 tarball 大小模拟 IPA 体积(源码注释说明这样做的目的是“验证 Dart 快照格式与数据布局的变化不会改变压缩体积”,同时近似 IPA 大小);
  • Windows:取生成的.exe文件大小。

APK 内产物的逐项大小则由getSizesFromApk得到:它运行unzip -v <apk>解析清单,提取lib/armeabi-v7a/libflutter.solib/armeabi-v7a/libapp.soassets/flutter_assets/NOTICES.Z的压缩/未压缩大小,产出libflutter_compressed_byteslibapp_compressed_bytes等指标(perf_tests.dart)。iOS 侧则从Frameworks/Flutter.framework/FlutterApp.framework/App读取未压缩大小(perf_tests.dart)。

使用建议:

  • 判断“整体是否变胖”看release_size_bytes两条曲线(Gallery 反映真实世界、hello_world 反映引擎基线);
  • 判断“引擎还是 Dart 侧变胖”看libflutter_*snapshot_*的相对变化;
  • 判断 ICU 数据变化看icudtl_*

对比 AOT 快照大小:定位到具体 Dart 方法

snapshot_*指标出现明显变化,或你主动修改了 Dart 代码/引擎编译器、想确认对 AOT 代码量的影响时,仓库提供了逐方法粒度的对比流程,完整步骤记录在 Comparing-AOT-Snapshot-Sizes.md。前提条件是该机器已完成引擎开发环境搭建(见 Setting-up-the-Engine-development-environment.md),下文用FLUTTER_ENGINE指代引擎源码根。

第 1 步:带--verbose构建 AOT 产物

对应用执行flutter build aot并加--verbose。目的是从日志里找到gen_snapshot的实际调用参数,然后补一个额外选项--print-instructions-sizes-to重新运行。如果用的是本地引擎构建,flutter build还需加上--local-engine--local-engine-host标志(参见 Debugging-the-engine.md 中“用本地引擎运行 Flutter 应用”一节)。

重新运行gen_snapshot的示例(SUMMARY_LOCATION是输出的 JSON 摘要文件路径):

$FLUTTER_ENGINE/out/host_debug_unopt/gen_snapshot \ --print-instructions-sizes-to=$SUMMARY_LOCATION \ --causal_async_stacks \ --packages=.packages \ --deterministic \ --snapshot_kind=app-aot-blobs \ --vm_snapshot_data=build/aot/vm_snapshot_data \ --isolate_snapshot_data=build/aot/isolate_snapshot_data \ --vm_snapshot_instructions=build/aot/vm_snapshot_instr \ --isolate_snapshot_instructions=build/aot/isolate_snapshot_instr \ build/aot/app.dill

各关键参数含义:

  • --print-instructions-sizes-to:把每个方法生成的指令大小按库/方法写入 JSON;
  • --snapshot_kind=app-aot-blobs:生成 AOT 应用的四个快照产物(VM/isolate 的 data 与 instructions);
  • 其余--vm_snapshot_*/--isolate_snapshot_*参数指定四个输出文件路径,build/aot/app.dill是输入 kernel 文件;
  • 其余参数(--packages--deterministic--causal_async_stacks)应与你从flutter build aot --verbose日志中看到的原始调用保持一致,这样才能公平对比。

SUMMARY_LOCATION文件另存为before.json

第 2 步:引入变更后重新生成

二选一:修改 Dart 代码(观察业务代码改动的影响),或重新构建引擎以获得新的gen_snapshot(观察编译器/运行时改动的影响)。

注意:再次执行flutter build aot这一步不可跳过,因为 AOT 编译在gen_snapshot之前还有一次 kernel 编译步骤(参见 Custom-Flutter-Engine-Embedding-in-AOT-Mode.md 中“生成 kernel 快照”一节),需要刷新 kernel 输入。重新运行gen_snapshot调用,把结果存为after.json

第 3 步:运行compare_size.dart生成差异表格

用引擎构建输出中的 Dart SDK 运行比较工具:

$FLUTTER_ENGINE/out/host_debug_unopt/dart-sdk/bin/dart \ before.json \ after.json

输出是按“库 / 方法 / 字节差”排序的表格,正数表示方法变大,负数表示变小,末尾给出总量变化。示例输出(截取自 Comparing-AOT-Snapshot-Sizes.md):

+------------+----------------------------------------------------------+--------------+ | Library | Method | Diff (Bytes) | +------------+----------------------------------------------------------+--------------+ | dart:async | new ZoneSpecification.from | +2136 | | dart:async | runZoned | +1488 | | dart:async | new _CustomZone | +927 | ... | | [Stub] Type Test Type: class 'PopupMenuItem' | -128 | | dart:io | new Directory | -211 | +------------+----------------------------------------------------------+--------------+ Total change +24036 bytes.

从示例可以看出两点:表格同时列出普通方法与[Stub](如类型测试 stub),因此能区分“真实代码变多”和“运行时 stub 增减”;每行给出精确的字节差,配合总量行即可回答“这次改动到底让 AOT 代码净增多少”。

工具链小结

场景工具入口
libflutter.so里什么组件占体积treemapCI 构建页index.html或 binary_size_treemap.sh
追踪提交间 APK/IPA 与libflutter.so、快照、icudtl.dat体积趋势devicelab 基准perf_tests.dart 上报的release_size_byteslibflutter_*snapshot_*icudtl_*指标
定位 AOT 快照变大的具体 Dart 方法gen_snapshot --print-instructions-sizes-to+compare_size.dartComparing-AOT-Snapshot-Sizes.md

三层工具自上而下构成完整的体积回归排查路径:先用 devicelab 曲线发现异常,再用 treemap 圈定引擎内的大头组件,最后用 AOT 快照对比把变化归因到具体方法,从而支撑对引擎磁盘占用的持续监控与问题定位。

【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询