28、App Bundle 与分发:Android App Bundle、动态功能模块
聊到应用分发,我记得几年前大家还在纠结 APK 怎么瘦身。后来 Google 推出了 Android App Bundle(简称 AAB),说实话,这玩意儿彻底改变了我的打包习惯。今天我们就来聊聊 AAB 和动态功能模块,看看它们到底解决了什么问题。
为什么要有 App Bundle?
你想想看,一个 APK 里其实塞了很多你根本用不上的东西。比如不同屏幕密度的资源、不同 CPU 架构的 so 库。用户手机是 ARM64 的,你非得把 armeabi、x86 的库都打包进去,这不是浪费流量和存储吗?
App Bundle 的核心思路很简单:把 APK 拆成基础模块 + 按需分发。你上传一个 AAB 到 Google Play,Google 会根据用户的设备配置,只生成对应的 APK。说白了,就是「千人千面」的分发策略。
核心优势:
- 应用体积平均减少 15% 以上
- 支持动态功能模块,按需下载
- Play Feature Delivery 提供多种分发模式
- Play Asset Delivery 支持大型资源包
AAB 文件结构长什么样?
我刚开始接触 AAB 时,第一反应是:这不就是个 ZIP 包吗?确实,AAB 本质上是一个 ZIP 文件,但它的内部结构很有讲究。
myapp.aab
├── base/ # 基础模块
│ ├── manifest/
│ ├── dex/
│ ├── res/
│ └── lib/
├── feature1/ # 动态功能模块
│ ├── manifest/
│ ├── dex/
│ └── res/
├── BundleConfig.pb # 配置文件
└── BUNDLE-METADATA/ # 元数据
嗯,这里要注意:base 模块是必须的,它包含了应用的核心代码和资源。动态功能模块是可选的,你可以按需添加。
动态功能模块实战
我在项目中遇到过这样一个场景:一个电商 App 的「AR 试妆」功能,只有 5% 的用户会用到。如果把这个功能塞进主 APK,那 95% 的用户都得为它买单。这时候动态功能模块就派上用场了。
第一步:创建动态功能模块
在 Android Studio 里,右键项目 → New → Dynamic Feature Module。系统会自动生成一个 feature 模块,并在主模块的 build.gradle 里添加依赖。
// app/build.gradle
android {
...
dynamicFeatures = [":ar_tryon"]
}
dependencies {
implementation project(":base")
...
}
第二步:配置分发模式
动态功能模块有三种分发模式,我建议你根据业务场景来选择:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| install-time | 安装时一并下载 | 核心功能,但不想增大主 APK |
| on-demand | 按需下载 | 低频功能,如 AR、高级滤镜 |
| conditional | 条件触发下载 | 特定国家、特定设备 |
// ar_tryon/build.gradle
android {
...
defaultConfig {
// 按需下载模式
missingDimensionStrategy "delivery", "on-demand"
}
}
// 或者在 AndroidManifest.xml 中配置
<dist:module
dist:onDemand="true"
dist:title="@string/ar_tryon_title">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
个人经验:fusing 属性一定要设为 true。这样当用户设备不支持按需下载时,可以退化为安装时下载。我曾经踩过这个坑,导致部分用户永远用不了这个功能。
第三步:代码中请求下载
按需下载的代码其实很简单,核心就是 SplitInstallManager。
// 请求下载动态模块
val manager = SplitInstallManagerFactory.create(context)
val request = SplitInstallRequest
.newBuilder()
.addModule("ar_tryon")
.build()
manager.startInstall(request)
.addOnSuccessListener {
// 下载成功,可以启动功能
startArTryonActivity()
}
.addOnFailureListener { exception ->
// 处理失败情况
Log.e("AAB", "下载失败: ${exception.message}")
}
为什么会失败?常见原因有:网络问题、存储空间不足、用户取消了下载。我建议你做好失败处理,给用户一个友好的提示。
SVG 流程图:App Bundle 分发流程
Play Asset Delivery:大型资源包怎么搞?
如果你的应用包含大型资源,比如 3D 模型、高清视频、游戏关卡,那 Play Asset Delivery(PAD)就是你的最佳选择。它和动态功能模块类似,但专门针对资源文件。
我个人习惯把资源包放在 src/main/assets/ 目录下,然后在 build.gradle 里配置:
// app/build.gradle
android {
...
assetPacks = [":asset_pack_1", ":asset_pack_2"]
}
PAD 也支持三种分发模式:install-time、on-demand 和 fast-follow。fast-follow 是个好东西,它会在应用安装后自动下载,但不会阻塞安装过程。适合那些「不是必须立即使用,但最好尽快准备好」的资源。
避坑指南:我曾经在 on-demand 模式下,忘记处理下载失败的回调。结果用户点了按钮,等了半天没反应,直接给了一星差评。记住:任何按需下载都要有进度提示和失败重试机制。
本地测试 AAB
你不可能每次都上传到 Google Play 去测试。Google 提供了 bundletool 这个命令行工具,可以在本地生成 APK 进行测试。
# 从 AAB 生成 APK
java -jar bundletool.jar build-apks \
--bundle=myapp.aab \
--output=myapp.apks \
--ks=mykeystore.jks \
--ks-pass=pass:123456
# 安装到设备
java -jar bundletool.jar install-apks \
--apks=myapp.apks
嗯,这里要注意:生成的 .apks 文件其实是个 ZIP 包,里面包含了所有设备配置对应的 APK。你可以解压看看,会发现针对不同屏幕密度、CPU 架构的 APK 体积差异很大。
总结一下
App Bundle 和动态功能模块,说白了就是「按需分配」的思想。你想想看,用户下载一个 100MB 的 App,结果 80% 的功能他根本不用,这多浪费。通过 AAB,我们可以做到:
- 基础模块:核心功能,必须安装
- 按需模块:低频功能,用户需要时再下载
- 条件模块:特定设备或地区才提供
我个人建议,新项目直接上 AAB,老项目可以逐步迁移。毕竟从 2021 年 8 月开始,Google Play 已经强制要求新应用使用 AAB 格式了。早点适应,总比被逼着改要好。
小技巧:在开发阶段,你可以在 Android Studio 的 Build → Build Bundle(s) / APK(s) → Build APK(s) 来生成传统 APK,方便调试。上线时再用 Build Bundle(s) 生成 AAB。
公众号:蓝海资料掘金营,微信deep3321