第2章 Android App Bundle 文件结构解析
Android App Bundle (AAB) 本质上是一个标准 ZIP 压缩包,但其内部文件组织和元数据遵循严格的协议缓冲区(Protobuf)格式。只有掌握 AAB 内部结构,才能理解动态交付的模块切割、资源拆分和多 APK 生成原理。本章逐层拆解 AAB 文件的根目录、base 模块、feature 模块、关键配置文件和资源索引,并通过 bundletool 命令验证结构。
2.1 顶层目录结构
使用 unzip -l *.aab 或 bundletool dump manifest --bundle 可以观察顶层布局。一个典型的 AAB 包含以下核心实体:
| 路径 / 文件 | 类型 | 作用 |
|---|---|---|
| BundleConfig.pb | Protobuf 文件 | 定义 bundle 版本、分发策略、优化指令 |
| base/ | 目录 | 基础模块,包含所有设备通用的代码和资源 |
| feature_*/ | 目录 | 按需交付的动态特性模块,名称由 dist:module 指定 |
| manifest/ | 目录 | 包含合并后的 AndroidManifest.xml |
| resources.pb | Protobuf 文件 | 全局资源索引表,记录所有资源的 ID、名称、限定符 |
| native.pb | Protobuf 文件 | 原生库元数据(可选) |
| dex/ | 目录 | DEX 分片描述文件(仅当启用分片时出现) |
bundletool dump manifest --bundle=/path/to/app.aab
bundletool dump resources --bundle=/path/to/app.aab
unzip -l /path/to/app.aab | head -30
2.2 base 模块内部结构
base 模块是每个 AAB 的必备模块,存放所有语言、屏幕密度、CPU 架构通用的代码与资源。其子目录结构如下:
base/
├── dex/ # DEX 字节码(按配置分割)
├── res/ # 资源文件(布局、图片、字符串等)
├── lib/ # 原生 so 库
├── assets/ # 应用资产
├── root/ # 放置在 APK 根目录的文件
├── manifest/ # 该模块的 AndroidManifest.xml
├── resources.pb # 该模块的资源索引子表
└── native.pb # 该模块原生库元数据(可选)
Gradle 构建时通过 android.baseModule 插件生成,默认包含应用启动所需的所有内容。
2.3 feature 模块(动态特性)
feature 模块对应 com.android.dynamic-feature 插件。每个动态特性模块拥有与 base 相似的子目录结构,但 manifest/AndroidManifest.xml 中必须声明 dist:module 和 dist:onDemand 属性。一个典型 feature 模块的清单片段:
<dist:module
dist:type="feature"
dist:title="@string/feature_name">
<dist:delivery>
<dist:on-demand/>
</dist:delivery>
<dist:fusing dist:include="true"/>
</dist:module>
base 与 feature 模块的关键区别:
| 属性 | base 模块 | feature 模块 |
|---|---|---|
| 分发时机 | 安装时强制包含 | 按需下载 / 条件分发 / 安装时可选 |
| 是否可移除 | 否 | 是(onDemand="true") |
清单中 dist:type | feature 且 dist:onDemand="false" | feature |
| 内容完整性 | 必须包含 Activity、Application 等入口 | 可只包含资源或代码 |
| 依赖关系 | 无 | 可依赖 base 或其他 feature |
2.4 BundleConfig.pb 配置详解
该文件使用 Protobuf 序列化,定义了 bundle 的构建设置。可通过 bundletool dump bundle-config --bundle 查看可读内容。关键字段:
- Bundletool.version:生成该 AAB 的 bundletool 版本。
- Optimizations:包含
splitsConfig(按资源/ABI 拆分)、uncompressNativeLibraries、removeUnusedResources等标记。 - AabConfig:模块分割规则,例如
standaloneConfig定义通用 APK 的生成方式。
<uncompressNativeLibraries>true</uncompressNativeLibraries> 会使原生库保持压缩,减小用户下载体积,但增加解压耗时。
2.5 资源表(resources.pb)
每个模块都有一个 resources.pb,同时 AAB 根目录也有一份全局聚合资源表。其记录了:
- 每个资源类型的 ID 范围(例如
0x7F010000-0x7F01FFFF属于 base 模块)。 - 每个资源的名称、类型、限定符(语言、密度、版本等)。
- 资源条目在 split APK 中的归属(用于按资源维度拆分)。
通过 bundletool dump resources --bundle 可以输出所有资源的结构化信息,这对调试资源缺失问题至关重要。
2.6 清单合并规则
多个模块的 AndroidManifest.xml 在构建 APK 时合并成单一清单。合并遵循以下优先级:
- base 模块的清单作为基础。
- feature 模块的清单通过
<dist:module>标签附加合并。 - 冲突属性(如
minSdkVersion)取最大值,<activity>等组件直接合并。 - merge tools 标记:使用
tools:replace、tools:node="merge"等控制合并行为。
最终清单中每个 <activity> 都会保留其所在模块的名称,Play Store 据此动态分配下载。
2.7 结构可视化:AAB 文件组织图
下图展示了 AAB 的核心架构,包含两个 feature 模块和一个 base 模块,以及全局配置文件:
2.8 模块分割规则与验证
Google Play 根据 AAB 中的模块信息生成多个 APK:基础 APK(包含 base 模块)、配置 APK(按密度、语言、ABI 拆分)、动态功能 APK(每个 feature 模块生成一个单独 APK)。开发者可通过以下命令验证分割结果:
# 生成所有 APK 到 output 目录
bundletool build-apks --bundle=/path/to/app.aab --output=app.apks
# 查看生成的 APK 列表
unzip -l app.apks | grep "splits/"
# 查看某个 feature 是否被正确提取
bundletool get-size --apks=app.apks --modules=base,feature_map,feature_pay
feature 模块的 AndroidManifest.xml 遗漏 <dist:module> 标签,或者 dist:onDemand 属性与预期不符,则 Play Store 可能拒绝上传或导致动态交付失效。务必在构建后检查 bundletool dump manifest 输出的清单内容。
2.9 本章小结
AAB 文件结构是动态交付的物理基础。开发者需要理解:
- base 模块与 feature 模块的目录差异。
- BundleConfig.pb 如何控制优化行为。
- resources.pb 和 manifest 合并是多 APK 生成的依据。