一、起点:一个 173MB 的 Android APK
工程是一个 Android 主工程,集成了同级目录的 Flutter module,外加两个本地子模块。ac flavor 的 release APK 重达 173 MB,远超合理范围。
二、排查过程(这部分是关键,比改动本身更值得记)
- 先体检 Flutter 集成方式
settings.gradle.kts 里看到:
· 一行硬编码的绝对路径引用 --- 只能在某台特定机器上跑
· 存在冗余的模块声明
· app/build.gradle.kts 里的注释与实际代码不一致,造成误导
- 第一次 clean release 构建:173 MB
体积异常大,开始定位。
- 拆 APK,找最胖的文件
unzip -l app-ac-release.apk → 按 lib/arm64-v8a/ 大小排序
· libflutter.so = 137.63 MB(正常应该 ~7--10 MB)
· libapp.so = 4.5 MB(Dart AOT 产物,正常)
- 排除 ABI 多打的可能
确认了 abiFilters 只保留 arm64-v8a 生效,APK 里只有一个 ABI 目录。问题锁定在单一 .so 文件本身。
- 检查 .so 的二进制属性
file libflutter.so → "with debug_info, not stripped"
带完整 DWARF 调试符号,未 strip。
- 验证手动 strip 可行
用 NDK 的 llvm-strip --strip-unneeded:138 MB → 11 MB。确认问题不在 .so 本身,而在构建流水线没自动 strip 它。
- 第一个错误假设:构建模式配置是元凶
在 Flutter module 的配置文件中看到某行配置,推测它强制了非 release 模式。删掉后重打 ------ 大小没变。假设证伪。后续确认打进 APK 的就是 release 引擎的原始未 strip 版本。
-
用 --info 跑 strip 任务找到铁证
Unable to strip library '.../libflutter.so' due to missing strip tool for ABI 'arm64-v8a'. Packaging it as is.
真因找到: 主工程模块没声明 ndkVersion,AGP 找不到 llvm-strip,对所有 prebuilt jniLibs(包括 libflutter.so、libapp.so 等)的 strip 静默跳过。Flutter 子模块虽然有 ndkVersion,但 strip 发生在主工程打包阶段,按主工程的配置走。
- 修复:加一行 ndkVersion,重打验证
173 MB → 46 MB,省了 127 MB。
三、最终改动清单(4 个文件)
- settings.gradle.kts
改动: 把硬编码绝对路径替换为通过 gradle.properties 配置的相对路径,加存在性校验,删冗余声明。
kotlin
// 改前:硬编码绝对路径 + 冗余 include
// 改后:
val flutterModulePath = (settings.extra.properties["flutter.module.path"] as? String)
?: "../FlutterModule"
val flutterIncludeScript = File(rootDir, "$flutterModulePath/.android/include_flutter.groovy")
require(flutterIncludeScript.exists()) {
"Flutter module not found. 请在 gradle.properties 设置 flutter.module.path"
}
apply(from = flutterIncludeScript)
收益: 其他开发者 / CI 不再受限于特定机器路径;新成员只需把 Flutter module 放同级目录即可,或在 ~/.gradle/gradle.properties 覆盖路径。
- gradle.properties
改动: 新增 Flutter module 路径属性(默认相对路径)。
flutter.module.path=../FlutterModule
- app/build.gradle.kts
改动 1: android {} 顶部加 ndkVersion。
kotlin
android {
ndkVersion = "28.2.13676358" // 让 AGP 能找到 llvm-strip
// ... 其他配置
}
改动 2: 移除误导性注释。
- Flutter module 的 .android/local.properties
改动: 删除非必要的构建模式配置行,让 Flutter 自然跟随 host 的 build type。
四、量化结果
指标 改前 改后 变化
APK 总体积 173 MB 46 MB −127 MB (−73%)
libflutter.so 137.63 MB 10.59 MB −127 MB (−92%)
libapp.so 4.50 MB 4.50 MB 持平
Clean 构建耗时 1m 33s 1m 09s 略快(少写 100+ MB IO)
五、后续可继续优化
- R8 + 资源压缩
在 release 构建类型中启用 isMinifyEnabled = true 和 isShrinkResources = true,配合 ProGuard 规则,可再砍掉 5--15 MB 的 Java/Kotlin 代码与无用资源。
- 改用 AAB 上架
Google Play 支持按设备配置切片下发,用户实际下载体积会进一步减小。
- 资源审计
扫描 res/raw、assets/ 目录,检查是否有冗余字体、动画、视频等大文件未使用。
- 图片资源优化
将 PNG 图片转为 WebP 格式,或使用矢量图替代位图。
- Flutter 产物进一步瘦身
· 启用 Flutter 的 --split-debug-info 选项分离调试符号
· 使用 --obfuscate 混淆 Dart 代码
六、教训 / 经验沉淀
· APK 异常大时,先 unzip -l 看分布,比盲目猜测高效得多
· Flutter 引擎 .so 原始 ~138 MB 是正常的,关键看是否被 strip
· ndkVersion 不只是给 native 代码模块用的 --- 只要工程有 prebuilt jniLibs(哪怕来自 Flutter 这类第三方),AGP 都需要它来定位 strip 工具,否则会静默跳过
· AGP 的 strip 失败信息默认不会出现在普通构建日志里,要加 --info 才能看到 "Unable to strip ... missing strip tool"
· 不要轻信可疑配置 --- 验证比假设重要,本次第一个猜测就被验证证伪了