Android App Bundle(AAB)是 Google Play 自 2021 年 8 月起对新应用强制要求的发布格式,也是同一个应用装在两台手机上体积可能完全不同的原因。你不再把编译好的 APK 直接交给 Google Play,而是提交一个 bundle,里面装着全部已编译代码、资源以及组装说明,由 Google Play 生成真正被安装到每台设备上的拆分 APK(split APKs)。
这一改动把许多决定从构建脚本搬到了 Play 侧:哪种 CPU 架构拿到哪些原生库、哪种屏幕密度拿到哪些图片、哪种语言拿到哪些字符串。它同时也带来了一组硬性体积上限——而且这些上限已经变了:base module 的上限不再是 200MB。仍按旧数字做方案的团队,要么把分发策略做得过度复杂,要么在上传时被一个看不出原因的体积错误卡住。
本文按你实际会遇到的顺序讲清 2026 年的全貌:bundle 里到底装了什么、当前的体积上限、拆分 APK 是怎么拼出来的、Play Feature Delivery 与 Play Asset Delivery 的区别、生成的 APK 用哪个版本号、如何用 bundletool 测试,以及上传前该跑的检查清单。
1. App Bundle 里到底装了什么
App bundle 就是你上传的 .aab 产物,用来替代 APK。Google 自己的说法很准确:它包含应用全部已编译的代码与资源,并且把 APK 的生成与签名交给 Google Play 完成。里面大致有四类内容:
- Base module(基础模块):所有设备都需要的代码、资源与清单文件。应用的 versionCode 与 versionName 只写在这里,这一点比听起来更重要。
- Feature module(功能模块):可以按安装时下发、按需下发或按条件下发的代码与资源。
- Asset pack(资源包):通过 Play Asset Delivery 分发的大文件,例如游戏资源、音视频素材、机器学习模型。
- 设备定向元数据:让 Play 判断「哪些内容属于哪台设备」的清单配置,包括 ABI、屏幕密度、语言,以及资源包可用的纹理压缩格式定向。
bundle 本身永远不会安装到设备上。它是分发的源头,不是安装包。
2. 「拆分」对用户意味着什么
用户点击安装时,Play 会针对这台设备的具体配置生成一组 APK、完成签名,然后下发。常见的拆分维度有:
- ABI 拆分:arm64-v8a、armeabi-v7a、x86_64。新手机只下载一种架构,而不是四种。
- 屏幕密度拆分:420 dpi 的机器不需要为 640 dpi 的位图付费。
- 语言拆分:设备没有要求的语言字符串留在服务器上。
- 动态模块与资源包拆分:任何你标记为按需下发的内容。
所以 Play Console 里的「下载大小」不是一个数字,而是横跨各种配置的一个区间;App size 页面会同时给出参考机型的数值和这个区间。用本地构建出来的单个 APK 判断整次发布,是安装转化率事后让人吃惊的常见原因。
3. 2026 年的体积上限
下面这些数字来自 Play Console 官方帮助文档,是应该拿来规划方案的版本。注意 base module 的上限早已不是老文章里写的 200MB。
- Base module:下载大小上限 500MB。
- 单个 feature module:500MB。
- 单个 asset pack:1.5GB。
- 全部模块 + 安装时(install-time)资源包累计:4GB。
- 按需(on-demand)与 fast-follow 资源包累计:30GB。
- 整体压缩下载大小上限:34GB。
与这些数字绑定在一起的还有四条约束:
- 应用超过 1GB 时,最低支持版本必须为 Android 5.0 Lollipop(API 21)或更高。
- 最低支持版本为 API 26 及以上时,建议的功能模块数量上限是 100;低于该版本时为 50。
- 单个 bundle 内的 asset pack 数量上限为 100。
- 应用超过 200MB 时,使用移动数据的用户在安装前会看到一条可跳过的体积提示。它不阻断安装,但确实会吃掉一部分转化。
一个历史遗留的坑:仍以普通 APK 形式交付的应用,不适用上面任何一条限制,而是继续沿用旧的 APK 规则——单个 APK 上限 100MB。如果上传报了一个像 2019 年才会出现的体积错误,先确认你上传的到底是不是 bundle。
4. 你需要的到底是哪一种机制
三种机制解决三类不同问题,混用会浪费数周时间。
- Play Feature Delivery:模块化的代码与资源。适合「并非每台设备、每个市场都该装」的功能。
- Play Asset Delivery:体积大的二进制素材。适合游戏内容、视频、音频、机器学习模型和大型数据集。
- Play App Signing:与上面两者并列但关键——使用 bundle 后由 Play 对生成的 APK 签名,这也是密钥与版本号管理方式改变的原因。
5. Play Feature Delivery:安装时 / 按需 / 条件
功能模块在清单文件中声明三种下发模式之一:
- install-time(安装时):属于首次安装的一部分,用户无法跳过,会计入用户看到的体积。
- on-demand(按需):应用主动请求后才下载。代码里必须处理等待中、下载中、已完成和失败四种状态。
- conditional(条件):只有在设备满足你声明的条件(设备特性、用户国家、最低 SDK 版本等)时,才在安装时下发。
条件下发是团队最容易忽略的一种。只服务平板的功能模块,或者只在某个支付渠道落地的市场才有意义的模块,没有理由装在每一台设备上。
6. Play Asset Delivery:安装时 / fast-follow / 按需
- install-time 资源包随应用一起安装,会计入应用的安装体积,也计入 4GB 累计上限。方便,但它仍然是商店页面上那个「应用大小」的一部分。
- fast-follow 资源包在安装完成后立即在后台自动开始下载。用户可以马上打开应用,素材随后到位。
- on-demand 资源包只在应用请求时下载。真正的大数字在这里:按需与 fast-follow 资源包拥有 30GB 的累计额度,正是因为它们不与安装转化率竞争。
资源包还支持纹理压缩格式定向,让 Play 按设备 GPU 实际支持的分发对应格式,而不是把全部格式都打进去。对游戏来说,这通常是单项收益最大的一次体积优化。
7. 生成的 APK 用哪个版本号
使用 bundle 之后,你不再为每个 APK 管理版本号。你只在 base module 里设置一个 versionCode,上传之后 Google Play 把它分配给由该 bundle 生成的所有 APK;同一次安装里的所有拆分 APK 共享同一个版本号。
带来的结果是「纪律」而不是「复杂」:要发新版本,就必须递增 base module 的 versionCode 并重新构建完整的 bundle;用户更新时,Play 会把设备上已安装的每个 APK 一起更新。由此有两条实践规则:
- 不要再沿用按 ABI 手写版本号的旧方案(经典的
abiCodes * 1000 + versionCode)。那是旧式 APK 拆分时代的做法,与 bundle 上传会直接冲突。 - 如果构建系统在不同轨道间把版本号弄乱,上传会以「version code 已被使用」或「低于现有版本」失败。base module 是唯一的权威来源。
8. 签名:上传密钥与应用签名密钥
启用 Play App Signing 后,你上传的产物用上传密钥(upload key)签名,Play 再用应用签名密钥(app signing key)重新签名生成的 APK。签名配置只应该写在 base module 里。两件事经常把人绊住:
- 上传密钥丢失可以走 Play Console 重置流程,是可恢复的;应用签名密钥则完全不需要你经手。
- 如果 bundle 用了错误的密钥签名,Play 会在进入审核之前直接拒收。这是构建配置问题,改一行即可,不是政策问题。
9. 发布前用 bundletool 测试拆分
Android Gradle 插件负责构建 bundle,bundletool 负责把它还原成具体设备的 APK,并在本地模拟 Play 的分发过程。Play 自己就是用同一个工具生成 APK 的,所以本地结果与线上行为高度一致。
# 生成覆盖所有支持设备配置的 APK 集合
bundletool build-apks \
--bundle=app-release.aab \
--output=app.apks \
--ks=upload-key.jks --ks-key-alias=upload
# 只部署与当前连接设备匹配的拆分
bundletool install-apks --apks=app.apks
# 生成一个 universal APK 做快速自检
bundletool build-apks --bundle=app-release.aab --output=app.apks --mode=universal
# 打开本地测试,用于验证按需模块与资源包
bundletool build-apks --bundle=app-release.aab --output=app.apks --local-testing
--local-testing 会把相关元数据写入 APK 清单,让 Play Core 库改用本地拆分而不是去连 Play。不加这个参数,按需下载相关代码就无法脱离商店验证。若要针对特定设备生成规格,用 bundletool get-device-spec。
正式全量发布之前,内部测试轨道仍然是最省事的验证方式,可以确认真实安装是否解析到了正确的拆分,包括你关心崩溃率的那批低端机型。
10. 先看 App size 页面,再谈优化
应用有正式发布后,Play Console 会在「Monitor and improve → Android vitals → App size」里显示下载大小:参考机型的数值、跨配置的区间、你自定义对照组的同类比较、随时间变化的趋势,以及两个比体积本身更有价值的指标:
- 剩余空间不足 2GB 的活跃设备占比——有多少安装量处于存储压力之下。
- 剩余空间不足 2GB 设备上的卸载率——这类设备上的卸载占全部卸载的比例。数值偏高,说明体积问题正穿着留存的衣服。
11. 上传前检查清单
- 上传产物是
.aab,不是 universal APK。 - base module 的 versionCode 只在一处递增。
- 上传密钥只配置在 base module 中,且密钥库有非单机备份。
- base module 压缩下载大小在 500MB 以内;若想避开移动数据体积提示,控制在 200MB 以内。
- 单个 feature module 在 500MB 以内;所有模块与安装时资源包累计在 4GB 以内。
- 单个 asset pack 在 1.5GB 以内;按需与 fast-follow 资源包累计在 30GB 以内。
- asset pack 数量不超过 100,功能模块数量按最低支持版本满足 100 或 50 的建议值。
- 应用超过 1GB 时,最低支持版本为 API 21 或更高。
- 按需功能通过 Play Core 库实现,并显式处理等待、下载中与失败状态。
- 存在多种 GPU 格式时,为资源包开启纹理压缩格式定向。
- 已用
bundletool install-apks在至少一台低端机与一台 arm64 机器上验证,并对每条按需路径跑过--local-testing。 - 发布上线后回看 App size 页面,包括剩余空间不足 2GB 的两项指标。
- 数据安全表单与内容声明仍然与新增模块实际收集的数据一致。
- 商店页面的文案与截图描述的仍是用户现在真正下载的版本,而不是你本地测试用的 universal 构建。
12. 只在发布之后才暴露的七种失败
- 按需模块始终不下载:Play Core 的接入假定请求一定成功,没有为失败或被取消的请求写重试路径。
- 安装时资源包让商店页体积暴涨:一次内容更新之后安装转化率下滑,而代码一行未改。
- 条件模块装到了不满足条件的设备上:条件写在了错误的清单文件里。
- 版本号冲突:CI 给 bundle 打版本号,而构建文件里还留着按 ABI 生成的旧方案。
- 密钥签名错误:更换密钥库后没有同步更新 base module 的配置。
- 弱网首次启动失败:应用首屏依赖一个还没下载完的 fast-follow 资源包。
- 数据安全表单不一致:新拆出的模块里落进了新的 SDK,但没有人重新声明。
13. KappS 在这里做什么
体积与下发方式,和开发者账号、付款资料、税务信息处在同一条运营链上:任何一环都可能悄悄卡住一次发布。KappS 帮助开发者在这条链上准备并提交合规材料,包括 bundle 与下发配置的复核——因为在安装时资源包和 fast-follow 资源包之间做选择,衡量单位是安装转化率,而不是兆字节。
我们不做的,是绕开商店。体积上限、下发声明、数据安全答案,都是 Google 会拿你实际提交的产物去核对的;一个声称了 bundle 并不会做的事的商店页面,通向的是被拒的发布或者被停用的账号,这不是我们提供的服务。真正的办法只有一个:如实构建 bundle,如实声明它包含什么,并尊重那些上限。
来源(Google 官方文档):About Android App Bundles(developer.android.com/guide/app-bundle);Play Console 帮助中心「优化应用体积并遵守 Google Play 体积上限」(support.google.com/googleplay/android-developer/answer/9859372);Configure the base module;Play Asset Delivery;Play Feature Delivery;bundletool(developer.android.com/tools/bundletool)。