很多团队做包体优化,第一反应是压图片、开 R8 、删无用资源。
这些当然要做,但它们更像打扫卫生。真正能把包体降下来的,往往不是“再抠 200KB”,而是重新回答一个问题:哪些东西真的必须跟着首装包一起交付?
包体优化的核心判断很简单:首装包不是仓库,它是一张成本账单。 谁把资源、代码、 SO 、 SDK 放进来,谁就要说明它服务了多少首启用户、多少核心路径,以及它有没有更便宜的交付方式。
先把常规操作打满,但别在这里恋战
常规动作要做,而且要自动化做。
第一层是构建侧: release 包开启 R8 ,打开 minifyEnabled 和 shrinkResources,检查 keep 规则是否过宽。 R8 不只是“混淆”,它会做代码裁剪、优化和资源缩减协同。 Android 官方文档也明确提到, R8 会移除不可达代码, AGP 的资源缩减会继续处理未使用资源。
第二层是资源侧: Lint 扫 UnusedResources,图片转 WebP 或 AVIF ,能用 VectorDrawable 的不要上多套 PNG ;按业务真实覆盖率收敛语言、 density 、 raw 、 font 、 Lottie 、动效素材。这里最容易出现的坑是:res 能扫,不代表 assets 也能扫干净。 很多大文件藏在 assets 、模型目录、模板包里,普通资源缩减不会自动帮你处理。
第三层是发布侧:如果走 Google Play ,优先使用 Android App Bundle 。 AAB 可以按设备配置下发更小的 APK ,比如 ABI 、屏幕密度、语言资源。注意它解决的是“同一个包里塞了太多设备用不到的东西”,不是替你判断某个业务功能应不应该首装。
第四层是 Native 侧: strip 掉 release 版 .so 的调试符号,确认 ABI 是否真的需要全量支持;如果项目里还有遗留的 x86 、 armeabi-v7a 或重复 C++ runtime ,要用数据说明保留价值。
这些做完,通常能拿到一波收益。但做到后半程,你会发现收益越来越像挤牙膏。
因为常规优化解决的是“无用”,而包体真正的大头往往是低频但有用。

非常规操作一:把包体拆成“首装账本”
包体优化最怕没有归属。
今天一个 SDK 加 2MB ,明天一个皮肤包加 4MB ,后天一个业务把测试素材带进 release 。每个人都觉得自己只加了一点,最后首装包变成公共垃圾场。
更有效的做法是把包体拆成账本:
然后给每个 owner 一个预算,不是总包体预算,而是增量预算。
比如:一个需求如果让首装下载增加 500KB ,就要在 MR 里解释三件事:这 500KB 属于核心路径吗?能否延迟下载?有没有低端机和弱网成本评估?
这一步听起来不像技术优化,但它很管用。因为包体膨胀大多数不是一次事故,而是持续没有守门。
我的建议是:包体报表不要只看总大小,要看 diff 。总大小只会让大家焦虑, diff 才能找到责任人。
这也是为什么工具要分层用,不要只打开一个 APK Analyzer 看总大小。
日常排查可以这样组合:
apkanalyzer:适合放进 CI ,输出 files 、 dex 、 resources 等维度;bundletool:看 AAB 按设备生成后的真实 APK 集合,不要只盯通用包;aapt2 dump resources:排查资源表、资源项和配置维度;dexdump 或 DEX 统计脚本:看方法数、类数、调试信息、行号信息;这里的重点不是工具名字,而是口径统一。没有统一口径的包体数据,很容易变成各说各话。 研发说 APK 小了,渠道说下载包没小;客户端说删了资源,业务说线上模板还在;最后所有人都没错,问题也没解决。
非常规操作二:把低频功能移出 base 包
很多 App 的 base 包里,塞着大量“用户可能会用,但不是第一次打开就要用”的能力:
如果这些东西都跟着首装包走,包体一定会被业务复杂度拖死。
更好的思路是把 base 包变成最小可运行内核:启动、登录、首页、核心交易链路、基础埋点、崩溃恢复必须稳定;其他能力按条件或按需交付。
Google Play 场景下,可以考虑 Play Feature Delivery ,把功能拆到 dynamic feature module ;重资产场景可以看 Play Asset Delivery 。非 Play 场景也可以做业务自己的资源包体系:远端 manifest 、按 hash 校验、灰度下发、失败回退到轻量默认资源。
这里要有边界。不要为了包体把关键路径拆得七零八落。能延迟下载的前提,是失败时用户还能继续完成核心任务。
举个常见判断:
包体优化不是把东西都扔到云端。它是把“首装必须有”和“用到再说”切清楚。

非常规操作三:治理 SO 和 SDK ,不要只删业务代码
很多包体大户不在 Java/Kotlin ,而在 .so 和三方 SDK 。
问题是 SO 往往没人敢动。业务说是基础能力,基础能力说是 SDK 自带, SDK 说删了可能崩。最后所有人都选择“先别动”。
这里可以按四步拆:
第一,做 ABI 账本。统计每个 ABI 下的库大小,确认线上分布。如果某些 ABI 只是历史包袱,就不要让所有用户为它付下载成本。 AAB 可以按设备分发,但如果你不走 Play ,渠道包、分包策略就要自己兜住。
第二,查重复和隐式依赖。很多 SDK 会带同名能力:图片解码、加密、日志、网络、播放器、地图定位。一个 App 里出现两套同类 Native 能力,通常比一段业务代码贵得多。
第三,拆低频 Native 能力。比如滤镜、美颜、 OCR 、离线语音、地图导航,如果不是基础路径,可以改成按需模块、独立插件或资源包。
第四,处理符号和打包方式。 release 版 .so 要 strip debug symbols ;对于现代 Android ,确认 useLegacyPackaging 配置,避免安装时把 .so 额外解压到文件系统带来更新成本。
需要提醒一句: SO 优化不能只看“包变小”。如果为了省体积换成运行时下载,必须补齐 ABI 校验、完整性校验、失败降级、灰度和回滚。 Native 层出问题,通常不是小事故。
前面单独拆过的 7z 压缩 SO ,可以放在这里理解:它不是基础瘦身,而是 Native 侧的最后一层压缩套利。
它适合的对象很窄:大、冷、独立、可延迟加载的 SO。 先做 ABI 拆分、 strip 、无用库删除,再看 7z 后的净收益。如果为了兜底同时保留原始 SO 和 7z 包,收益会被吃掉;如果 SO 在启动链路上,解压、校验、落盘和加载顺序又会把体积收益转成启动成本。
所以 7z 压 SO 的正确姿势不是“全量压一遍”,而是拿一个冷路径大 SO 试点:构建期按 ABI 打包,运行时按需解压到私有目录,逐个校验 hash ,记录版本和 mapping ,并监控加载失败率、首启耗时、磁盘占用、解压耗时。只要这些指标有一个失控,就应该退回普通 SO 治理。

非常规操作四:收敛 keep 规则和生成代码
很多项目开了 R8 ,却没有真正享受到 R8 的收益。
原因不是 R8 不够强,而是 keep 规则太“豪放”。
常见写法是把某个业务包、 SDK 包、路由包整段 keep 住,所有类和成员都不让 R8 动。
这类规则一多, R8 只能站在门口看着包体膨胀。尤其是反射、序列化、路由、插件化、埋点、热修复相关模块,很容易为了“先不崩”把整个包都保护起来。
更好的做法是把 keep 规则变成可审查资产:
还有一个包体暗坑是生成代码。
Protocol Buffers 、 JSON adapter 、路由表、 DI 容器、埋点枚举、接口 Mock 、调试菜单,都会悄悄生成大量类和方法。官方文档也提醒过,某些生成代码会显著扩大 App footprint 。我的经验是:生成代码不是免费代码,它只是换了一个入口进入包体。
如果某个协议只在服务端使用,不要把全量 schema 带进客户端;如果某个路由只在 debug 用,不要进 release ;如果枚举只是为了日志可读,线上可以考虑更轻的编码方式。
Redex 也可以放在这一层看。
R8 是 Android 构建链路里的默认主力, Redex 更像 DEX 产物后的二次优化器。它会读取、分析、改写 .dex,典型收益点包括方法内联、类合并、去虚调用、删除不可达代码、移除无用接口、调整布局、去除部分 debug 信息和优化行号信息。 Meta 开源时给它的定位就是 Android bytecode optimizer 。
但 Redex 不建议一上来全量激进开启。原因也很现实:越靠近字节码后处理,越要为可调试性和兼容性买单。
比如内联能减少调用和方法体开销,但堆栈会变得不直观;去掉 debug info 和行号能省体积,但崩溃定位、灰度排障、线上符号化都要重新验证;类合并和接口删除可能碰到反射、序列化、 JNI 、插件化、热修复。 Redex 官方示例也提醒过,某些优化需要通过 ProGuard 规则控制,否则反射或 instanceof 场景可能不安全。
我会把 Redex 的接入顺序放在 R8 之后:先让 R8 、 keep 收敛、生成代码裁剪稳定,再选一组低风险 pass 做 A/B 。上线前至少要验证启动、崩溃堆栈、混淆 mapping 、反射入口、热修复、插件化和低端机安装。 Redex 能带来收益,但它不是“开关型优化”,更像一套需要长期维护的 DEX 手术台。
非常规操作五: ResGuard 不是压图,而是资源表手术
前面写过的 AndResGuard ,可以和这一节关联起来看。
R8 和 shrinkResources 主要解决“代码引用不到的资源”。 AndResGuard 盯的是另一件事:资源文件名、路径、resources.arsc、 APK 重打包、签名和 mapping 。它不是把某张图再压小一点,而是把资源侧的长路径、长名字和资源表冗余继续压缩。
它适合的项目特征也很明确: res 目录很大、资源命名长、历史资源多、多业务模块堆叠,并且你已经做过无用资源清理和图片格式优化。
但 AndResGuard 的风险也不能轻描淡写:
resources.arsc 是否压缩要谨慎,极限收益可能换来兼容性问题;所以 ResGuard 不是常规第一步。它更适合放在“资源清理完成之后”的产物层优化:先删无用资源,再压图片,再谈资源混淆。 如果基础账本还没算清,直接上 ResGuard ,很容易把问题藏起来,而不是解决掉。
非常规操作六:把资源做成“可替换资产”,不是“永久内置资产”
资源优化最容易陷入工具流:压缩图片、转格式、删重复。
但更大的收益来自资产策略。
比如活动背景、运营模板、 Lottie 动效、贴纸、字体、音效、城市数据、帮助文档,这些资源的生命周期往往比 App 版本短。它们进包之后,要等下个版本才能删;它们外置之后,可以按灰度、地域、活动周期动态替换。
一个比较稳的资源外置方案至少要有:
这里最大的坑是只做下载,不做治理。结果包体是小了,启动时多了一堆网络请求,弱网下页面白屏,用户体验反而变差。
所以资源外置的判断标准不是“能不能下载”,而是下载失败时能不能优雅地不用它。
非常规操作七: CI 里卡住包体回归
包体优化不能靠专项战役。
专项战役的典型结局是:两周瘦身 10MB ,三个月涨回 12MB 。
真正能长期有效的是 CI 守门:
我建议至少设置三条线:
第一条是 warning 线,比如单 MR 增量超过 200KB 自动提醒;
第二条是 block 线,比如超过 1MB 必须架构或性能 owner 审批;
第三条是 rollback 线,如果某个版本包体增长导致安装转化或启动指标明显恶化,要能快速回滚或拆分。
包体优化最怕“优化时很专业,合入时没人管”。 CI 守门的价值,就是把包体从专项指标变成日常工程纪律。

容易被忽略的几个坑
第一,不要盲目删除 Baseline Profile 。 Profile 文件会增加一点包内容,但它服务的是启动和运行性能。包体和性能不是天然同向,删之前要用 Macrobenchmark 或线上指标证明收益大于损失。
第二,不要把所有图片都转成 VectorDrawable 。简单图标适合,复杂插画不一定适合。 Vector 可能让绘制成本上升,甚至 XML 比压缩后的位图还大。
第三,不要把“远端化”当万能药。远端资源会带来下载失败、缓存膨胀、版本兼容、灰度污染和安全校验成本。
第四,不要把 Redex 、 ResGuard 、 7z 当免费午餐。它们都是产物层手术,收益越靠后,回归验证成本越高。
第五,不要只看 APK 文件大小。对用户更关键的是下载大小、安装后占用、首次启动依赖、更新包增量、弱网下资源命中率。
第六,不要相信一次性清理。包体增长是持续发生的,治理也必须持续发生。
最后给一套执行顺序
如果你现在要做包体优化,我会按这个顺序推进:
先做账本,拆 DEX 、 SO 、 res 、 assets 、 SDK ,建立 owner 和 diff 。
再打满常规项: R8 、资源缩减、图片格式、无用资源、 ABI 、 AAB 、符号裁剪。
然后重点攻非常规项:低频功能移出 base ,重资产资源外置, Native 能力按需, keep 规则收敛,生成代码裁剪。
最后再碰产物层手术: Redex 优化 DEX , AndResGuard 优化资源表和资源路径, 7z 压缩冷路径大 SO 。顺序不能反。先把账算清,再决定要不要动刀。
最后把 CI 守门补上,不让包体在后续版本里悄悄涨回来。
一句话收尾:包体优化不是把 App 做小,而是让每 1KB 都有理由待在首装包里。
参考资料:
https://developer.android.com/topic/performance/reduce-apk-size
https://developer.android.com/topic/performance/app-optimization/enable-app-optimization
https://developer.android.com/guide/app-bundle
https://developer.android.com/guide/playcore/feature-delivery
https://developer.android.com/topic/performance/baselineprofiles/overview
https://github.com/facebook/redex
https://engineering.fb.com/2016/04/12/android/open-sourcing-redex-making-android-apps-smaller-and-faster/
https://fbredex.com/docs/examples/proguard/
https://github.com/shwenzhang/AndResGuard/blob/master/README.zh-cn.md
夜雨聆风