上线第一天看到用户评论"一个工具App要200MB?抢钱吗",我脸都红了。打开构建分析一看,assets目录里的图片占了120MB,没用到的abi全打包进去了,字体文件一套塞了5个。

咱们今天直接把包体积从200MB砍到50MB以下,3步搞定,下载转化率肉眼可见地上涨。
一、先看真相:你的App里到底装了什么
别瞎猜,Flutter自带构建分析工具,打开终端跑:
●●●bash flutter build apk --analyze-size=build/apk_size.json
然后上传到官方分析器:
●●●bash # 用浏览器打开 https://flutter.github.io/split-per-abi/
# 或者直接查看分析报告
flutter build apk --split-per-abi
跑完你会看到类似这张表:
●●●code Total APK size: 198MB
├── lib/ (native libs): 65MB
├── assets/: 120MB
├── Flutter framework: 8MB
├── Dart code: 3MB
└── 其他: 2MB
老实说,Dart代码只占3MB,真正拖后腿的是资源和native库。找到大头,才有方向。
二、第一步:拆分ABI包(效果最猛,直接砍一半)
Flutter默认打出来的apk包含所有CPU架构的so库——armeabi-v7a、arm64-v8a、x86_64全塞进去。但用户的手机只需要其中一个。
❌ 默认全包写法:
●●●bash flutter build apk --release
# 输出: app-release.apk (198MB) — 所有abi都在里面
✅ 按ABI拆分:
●●●bash flutter build apk --split-per-abi --release
输出三个独立的apk:
●●●code app-armeabi-v7a-release.apk 68MB (32位老机型)
app-arm64-v8a-release.apk 72MB (64位主流机型)
app-x86_64-release.apk 58MB (模拟器用)
直接砍掉60%以上的体积。现在Google Play和华为/小米应用商店都支持自动按用户设备分发对应abi的包,上传这三个就行了。
如果只用一个渠道分发(比如企业内部分发),只保留arm64-v8a就够了,现在99%的新设备都是64位。
在 android/app/build.gradle 里确认:
●●●gradle android {
defaultConfig {
// 确保只打包64位
ndk {
abiFilters 'arm64-v8a'
}
}
}
这一步做完,198MB → 72MB,效果立竿见影。
三、第二步:压缩图片资源(120MB → 15MB的秘诀)
这是最多人踩的坑。设计师给一套PNG图标,每张2~5MB,全丢进assets目录,编译时原样打包。
方案A:转WebP(推荐)
WebP是Google亲儿子格式,Flutter原生支持,不用任何额外插件。体积比PNG小60~80%。
●●●bash # macOS安装转换工具
brew install webp
# 批量转换assets下所有PNG
find assets/ -name "*.png" -exec sh -c \
'cwebp -q 80"$1" -o "${1%.png}.webp" && rm "$1"' _ {} \;
转换后更新 pubspec.yaml 里的引用:
●●●yaml flutter:
assets:
- assets/images/
代码里不需要改,Image.asset('assets/images/logo.webp') 直接用。
方案B:移除未使用的资源
很多项目里躺着半年前设计稿的图片,早就没人用了但没删。用这个脚本扫一遍:
●●●bash #!/bin/bash
# 查找assets里有但代码里没引用的图片
cd assets/images
for f in *.webp *.png *.jpg; do
name=$(basename "$f")
if ! grep -rq "$name" ../../lib/; then
echo "未引用: $name"
fi
done
老实说,咱们跑完这个脚本,删掉了47张没人用的图,直接省了60MB。
方案C:用网络CDN代替本地资源
启动图、引导页图片、运营活动图——这些不该打包进App。放CDN上,按需加载:
●●●dart // 运营图片从CDN加载,不占安装包体积
Image.network(
'https://cdn.yourdomain.com/promo/banner_2026.webp',
cacheWidth: 720, // 关键:限制解码尺寸
cacheHeight: 400,
);
这一步做完,assets从120MB降到15MB以内。
四、第三步:裁剪字体和移除无用代码
字体瘦身
一套中文字体(比如思源黑体)完整打包要20MB以上。用fonttools只保留用到的字符:
●●●bash # 安装fonttools
pip install fonttools brotli
# 从完整字体中抽取只包含常用汉字的子集
pyftmerge \
--output-file=assets/fonts/NotoSansSC-subset.ttf \
--unicodes="U+4E00-U+9FFF,U+3000-U+303F,U+FF00-U+FFEF" \
assets/fonts/NotoSansSC-Regular.ttf
提取后的子集字体通常只有2~3MB,覆盖了99%的日常使用场景。
开启代码混淆和Tree-shaking
Flutter release模式自动做tree-shaking,但很多人开发时引了一堆只在debug用的包:
●●●yaml # pubspec.yaml
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0
provider: ^6.1.1
dev_dependencies:
flutter_test:
sdk: flutter
# 这些只在开发时用,不会打进release包
flutter_lints: ^3.0.0
build_runner: ^2.4.8
json_serializable: ^6.7.1
确认 flutter build apk --release 时没有warning提示未使用的依赖。
构建时加上混淆参数:
●●●bash flutter build apk --release --obfuscate --split-debug-info=build/symbols
--obfuscate 会混淆Dart代码中的符号名,--split-debug-info 把符号表单独存一份(崩溃分析时用),apk体积再少几MB。
五、效果对比汇总
ABI拆分:198MB → 72MB(↓64%)
图片压缩(WebP + 清理):72MB → 48MB(↓33%)
字体裁剪 + 代码混淆:48MB → 42MB(↓12%)
总计:200MB → 42MB,缩减79%
避坑清单(血泪经验)
1.别用 flutter build apk 不加 --release — debug模式的apk比release大3倍以上,因为包含了完整的Dart VM和调试符号。发给用户的永远是 --release。
2.iOS的ipa也要做类似优化 — iOS用的是 flutter build ipa,同样可以用 --split-debug-info。另外iOS的资源压缩用Xcode的 Compress PNG Files 选项自动处理。
3.WebP兼容性不是问题 — Flutter从2.0开始原生支持WebP,Android 4.0+和iOS全版本都能正常解码。不用担心老设备。
4.CDN资源要加降级策略 — 网络图片加载失败时,给个占位图,别让用户看到空白:
●●●dart Image.network(
'https://cdn.yourdomain.com/banner.webp',
errorBuilder: (context, error, stackTrace) {
returnContainer(
height: 200,
color: Colors.grey[200],
child: constCenter(child: Text('图片加载失败')),
);
},
);
老实说,包体积这件事不是"越小越好",而是"不该大的别大"。Dart代码本来就只有几MB,没必要在上面折腾。真正该砍的是资源文件和多余的abi库。3步做完,你的App从200MB掉到40MB出头,用户下载意愿至少翻一倍。
你的App安装包现在多大?跑完构建分析后最大的那块是什么?在评论区说说你的数据,咱们一起看看还能怎么砍。
夜雨聆风