第4章:定义动态模块:Android模块创建、build.gradle配置、模块类型与分发条件
4.1 原理说明:动态模块的编译与分发模型
动态功能模块(Dynamic Feature Module)是Android App Bundle格式的核心组成部分。与传统的APK多模块不同,动态模块在构建时不会被合并到基础APK中,而是以独立的拆分APK形式存在于App Bundle内。当用户触发按需加载条件时,系统通过Play Core SDK向Google Play Store发送下载请求,Play Store返回对应配置的拆分APK并安装到设备。
动态模块的分发行为由分发条件(dist:module 与 dist:delivery 标签)控制,在模块的 AndroidManifest.xml 中声明。分发条件决定了模块何时可用、如何下载、是否自动更新等关键属性。
核心原则: 每个动态模块对应一个拆分APK,模块的
build.gradle 必须引用 com.android.dynamic-feature 插件,并且在基础模块的 build.gradle 的 dynamicFeatures 列表中列出。所有模块(包括基础模块)必须使用App Bundle打包格式。
4.2 实现流程:从创建到配置的完整步骤
- 创建动态模块:在Android Studio中通过 File > New > New Module > Dynamic Feature Module 向导创建,或手动在项目根目录下创建模块目录并配置。
- 配置模块的
build.gradle:应用com.android.dynamic-feature插件,指定模块依赖基础模块。 - 声明分发条件:在模块的
AndroidManifest.xml的<dist:module>和<dist:delivery>标签中定义模块类型(按需/条件/随时)及分发属性。 - 关联基础模块:在基础模块(app)的
build.gradle的android.dynamicFeatures中添加模块名。 - 打包与验证:使用
./gradlew :app:bundleRelease生成App Bundle,检查output目录下是否包含对应拆分APK。
4.3 代码与配置示例
4.3.1 动态模块的 build.gradle 配置(以模块名 ondemand_feature 为例)
apply plugin: 'com.android.dynamic-feature'
apply plugin: 'kotlin-android'
android {
compileSdk 34
defaultConfig {
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// 必须与基础模块的buildTypes/signingConfigs保持一致
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// 动态模块必须依赖基础模块
implementation project(':app')
// 可选:动态模块内部使用的其他库
implementation 'androidx.core:core-ktx:1.12.0'
}
4.3.2 基础模块(app)的 build.gradle 中声明动态模块
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
...
// 声明动态模块列表
dynamicFeatures = [':ondemand_feature', ':conditional_feature']
// 必须启用App Bundle打包格式
bundle {
language {
enableSplit = false
}
density {
enableSplit = true
}
abi {
enableSplit = true
}
}
}
dependencies {
implementation project(':ondemand_feature')
implementation project(':conditional_feature')
implementation 'com.google.android.play:feature-delivery:2.0.1'
implementation 'com.google.android.play:feature-delivery-ktx:2.0.1'
}
4.3.3 模块的 AndroidManifest.xml 中设置分发条件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.app.ondemand_feature">
<dist:module
dist:instant="false"
dist:title="@string/module_title_ondemand">
<dist:delivery>
<dist:on-demand /> <!-- 按需下载 -->
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
<application>
<activity android:name=".OndemandActivity"
android:exported="false" />
</application>
</manifest>
分发条件说明:
•
•
•
•
<dist:on-demand /> — 用户主动触发下载,模块不会预加载,需调用 splitInstallManager.startInstall()。•
<dist:install-time /> — 模块在安装应用时一并下载(类似传统APK),不可卸载。•
<dist:conditional /> — 需配合 <dist:conditions> 子标签定义条件(如设备国家/语言/API级别),满足条件后自动下载。
4.3.4 条件分发示例(conditional_feature 模块的 Manifest)
<dist:module dist:instant="false">
<dist:delivery>
<dist:conditions>
<dist:user-countries dist:exclude="false">
<dist:country dist:code="US" />
<dist:country dist:code="CA" />
</dist:user-countries>
<dist:min-sdk dist:value="26" />
</dist:conditions>
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
4.4 动态模块类型与分发条件对比表
| 模块类型 | dist:delivery 标签 |
下载时机 | 是否可卸载 | 典型应用场景 |
|---|---|---|---|---|
| 按需(On-demand) | <dist:on-demand /> |
运行时开发调用 startInstall() |
是 | 付费功能、高级滤镜、小游戏 |
| 安装时(Install-time) | <dist:install-time /> |
用户安装主应用时一并安装 | 否(与主应用共存亡) | 核心功能(基础UI、公共库) |
| 条件(Conditional) | <dist:conditions>...</dist:conditions> |
满足条件后自动下载,无需用户主动触发 | 是(由系统管理) | 地区限制功能、设备特定功能(ARCore依赖) |
| 即时(Instant) | dist:instant="true" + <dist:instant /> |
即时应用体验时加载 | 仅限于即时应用(无安装) | 试用版、免安装体验 |
重要限制:
1. 安装时模块(install-time)不支持通过
2. 条件模块的
3. 所有动态模块的名称在
1. 安装时模块(install-time)不支持通过
SplitInstallManager 更新或替换;它们属于主应用的一部分。2. 条件模块的
dist:country 只支持 ISO 3166-1 alpha-2 代码;dist:min-sdk 不能低于基础模块的 minSdk。3. 所有动态模块的名称在
dynamicFeatures 列表中必须使用 : 前缀(如 ':ondemand_feature'),且模块路径不能包含大写字母。
4.5 调试方法
- 本地验证模块结构:使用
bundletool检查生成的App Bundle中是否包含对应拆分APK:
java -jar bundletool-all.jar get-device-spec --output=spec.json java -jar bundletool-all.jar build-apks --bundle=app/build/outputs/bundle/release/app-release.aab --output=app.apks --overwrite unzip -l app.apks | grep -i split_ondemand - 模拟按需安装:在测试设备上部署调试基座APK,通过
adb install-multiple手动推送拆分APK:
adb install-multiple app-release.apk split_ondemand.apk - Play Console内部测试:上传App Bundle到开放式测试轨道,使用测试账号下载,在
adb logcat中过滤SplitInstall日志。 - 快速迭代配置:修改分发条件后,无需清理构建,直接执行
./gradlew :app:bundleDebug重新打包。
4.6 部署步骤
- 在Google Play Console中创建应用,开启“高级功能”中的“动态功能模块”。
- 上传包含动态模块的App Bundle(
.aab)。 - 配置发布轨道(内部测试/封闭测试/正式发布)。
- 确保签署证书的密钥与测试设备上安装的应用签名一致。
- 使用测试账号安装应用,触发模块下载验证。
4.7 常见错误处理
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 编译报错“Dynamic feature module must depend on a base module” | 动态模块的 build.gradle 中未添加 implementation project(':app') |
在动态模块的依赖中添加基础模块引用 |
打包后 aab 中找不到拆分APK |
基础模块的 dynamicFeatures 列表忘记添加模块名 |
检查基础 build.gradle 中 android.dynamicFeatures 是否包含 ':模块名' |
安装后模块无法下载,返回 SESSION_NOT_FOUND |
分发条件使用了 <dist:install-time /> 却被当作按需模块调用 |
确认模块Manifest中的 <dist:delivery> 标签是否正确 |
| 条件模块未自动下载 | 设备不满足条件(如国家/地区/API级别) | 使用模拟器设置符合条件的环境,测试 adb shell getprop 检查语言和国家 |
| ProGuard混淆导致模块内类找不到 | 动态模块的ProGuard规则未保留模块入口类 | 在动态模块的 proguard-rules.pro 中添加 -keep class com.example.app.ondemand_feature.** |
4.8 性能与稳定性优化
- 减少模块体积:避免将基础模块中已存在的库重复打包到动态模块;使用
provided或compileOnly依赖基础模块中的类。 - 延迟初始化:在动态模块的
Application或Activity中采用AppCompatDeferred模式,避免加载时阻塞UI线程。 - 条件分发最小粒度:尽量使用
<dist:min-sdk>和<dist:user-countries>组合,减少不必要的自动下载流量。 - 打包配置优化:基础模块中
bundle.abi.enableSplit = true可以针对不同CPU架构生成拆分APK,但动态模块的配置应保持一致,避免架构不匹配导致下载失败。 - 状态持久化:在
SplitInstallStateUpdatedListener中记录模块下载状态到SharedPreferences,避免每次启动都重复查询。