第4章 模块定义与Gradle配置:动态功能模块 & dist:install-time vs on-demand
动态交付的核心是「按需下载运行,而非安装时打包」。Android 通过 Split Install API 和 Gradle 动态功能模块 实现这一目标。本章从模块定义出发,深入 dist:install-time 与 dist:on-demand 的机制差异,并给出完整配置示例与调试方法。
4.1 动态功能模块的结构
每个动态功能模块是一个独立的 Gradle module,其 build.gradle 中仅需声明 com.android.dynamic-feature 插件,并定义模块名称与分发类型。
模块内文件结构与普通 com.android.library 一致,可包含资源、AndroidManifest.xml、res、src 等。关键区别在于 AndroidManifest.xml 中必须配置 dist:module 属性。
结构化要点:
- 模块类型:
com.android.dynamic-feature(区别于 application / library) - 分发属性:
dist:install-time或dist:on-demand(二选一,互斥) - 模块名称:
dist:module name="xxx"用于运行时唯一标识 - 依赖关系:动态模块可依赖 base module 或其他 library,但 base 不能反向依赖动态模块
4.2 dist:install-time 与 dist:on-demand 深度对比
| 属性 | install-time | on-demand |
|---|---|---|
| 安装时机 | 用户安装 APK 时自动下载并集成 | 用户首次安装时不下载,仅保留占位 |
| 运行时下载 | 不需要(已安装) | 需要通过 SplitInstallManager 触发下载 |
| 大小限制 | 受 APK 签名大小限制(通常 < 2 GB) | 每个 on-demand 模块最大 512 MB(Google Play 限制) |
| 卸载能力 | 不可单独卸载,只能卸载整个应用 | 用户可在系统设置中手动卸载动态模块 |
| 适用场景 | 核心功能、必须随应用提供的资源 | 低频功能、大容量资源、A/B 测试内容 |
| 清单配置示例 | <dist:module dist:install-time="true"> |
<dist:module dist:on-demand="true"> |
4.3 Gradle 配置完整示例
以下展示 base module 与动态功能模块的 build.gradle 协作配置(Kotlin DSL 格式)。
4.3.1 Base module (app/build.gradle.kts)
plugins {
id("com.android.application")
}
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.dynamicdemo"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
// 启用动态功能
dynamicFeatures = setOf(":feature_ar", ":feature_pdf")
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
// 不直接依赖动态模块,只依赖 play 部分
implementation("com.google.android.play:feature-delivery:2.1.0")
}
4.3.2 On-demand 模块 (feature_ar/build.gradle.kts)
plugins {
id("com.android.dynamic-feature")
}
android {
compileSdk = 34
namespace = "com.example.dynamicdemo.feature_ar"
}
dependencies {
implementation(project(":app"))
// 可添加该模块独有的依赖
implementation("com.google.ar:core:1.38.0")
}
4.3.3 Install-time 模块 (feature_pdf/build.gradle.kts)
plugins {
id("com.android.dynamic-feature")
}
android {
compileSdk = 34
namespace = "com.example.dynamicdemo.feature_pdf"
}
dependencies {
implementation(project(":app"))
implementation("com.itextpdf:itext7-core:7.2.5")
}
4.4 AndroidManifest.xml 中的分发属性
每个动态功能模块的 AndroidManifest.xml 必须包含 <dist:module> 元素,位于 <manifest> 根节点下,不嵌套在 <application> 内。
常见错误:混淆 dist:install-time 和 dist:on-demand 的 XML 命名空间。必须使用 http://schemas.android.com/apk/distribution 预定义命名空间,且标签名对应为 dist:module、dist:install-time、dist:on-demand,大小写敏感。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.dynamicdemo.feature_ar">
<dist:module
dist:on-demand="true"
dist:title="@string/feature_ar_title">
<dist:delivery>
<dist:install-time />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
<application ... />
</manifest>
属性说明:
dist:on-demand="true"启用按需模式(与dist:install-time="true"互斥)<dist:install-time />内嵌在<dist:delivery>中,指示分发方式(可嵌套多个方式)dist:fusing允许模块在旧设备(不支持 Split APK)时与 base 融合安装dist:title为用户可见的模块名称(用于系统设置卸载时的标识)
4.5 构建与调试要点
- 生成 Split APK:使用
./gradlew bundle或Build > Build Bundle(s) / APK(s) > Build APK(s),Android Studio 会自动为每个动态模块生成独立的.apk分片。 - 本地测试:使用 Android Studio 的 “Run” 按钮默认仅部署 base + install-time 模块。要测试 on-demand 模块,需通过
SplitInstallManagerAPI 或使用bundletool安装完整应用。 - bundletool 命令示例:
java -jar bundletool.jar build-apks --bundle=app.aab --output=app.apks
java -jar bundletool.jar install-apks --apks=app.apks
调试建议:在开发阶段可将 on-demand 模块暂时改为 dist:install-time="true",以验证模块内容是否正确。正式发布前改回。注意 Google Play 控制台不显示 on-demand 模块的下载状态,测试需使用 bundletool 或在设备通过 adb install-multiple。
4.6 常见配置错误与修复
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
构建失败:Multiple deliveries specified |
同时声明了 dist:install-time 和 dist:on-demand |
只保留一种分发属性 |
运行时 SplitInstallException: MODULE_UNAVAILABLE |
模块名与 dist:name 不匹配,或模块未发布到 Play Store |
检查 dist:module name 与 SplitInstallManager 传参一致 |
| 安装后模块内容丢失 | dist:fusing 未正确配置 |
在 <dist:module> 内添加 <dist:fusing dist:include="true" /> |
| 模块无法卸载 | 模块被设为 dist:install-time |
使用 dist:on-demand 方可卸载 |
4.7 性能与稳定性优化
- 按需模块大小控制:每个 on-demand 模块建议不超过 200 MB,避免下载超时。超过 512 MB 会被 Google Play 拒绝。
- 预加载策略:对高频 on-demand 模块,可在应用启动后通过
SplitInstallManager.deferredInstall()提前下载到缓存区,提升用户体验。 - 错误处理:在
onError()回调中捕获SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED(同时下载会话不得超过 10 个)。 - 模块拆分粒度:避免将原子功能拆成多个模块,因为每次 on-demand 下载都有网络开销。建议按「用户场景」而非「技术组件」划分。
最佳实践:在 Gradle 配置中为 on-demand 模块做 资源裁剪,通过 resConfigs 限制仅包含目标语言和密度,减少模块体积。
android {
defaultConfig {
resConfigs "en", "zh-rCN"
}
bundle {
language {
enableSplit = true
}
density {
enableSplit = true
}
}
}