第4章 模块定义与Gradle配置:动态功能模块 & dist:install-time vs on-demand

动态交付的核心是「按需下载运行,而非安装时打包」。Android 通过 Split Install API 和 Gradle 动态功能模块 实现这一目标。本章从模块定义出发,深入 dist:install-time 与 dist:on-demand 的机制差异,并给出完整配置示例与调试方法。

动态功能模块分发模式对比图 install-time 模块 dist:install-time="true" 🔒 安装时一并下载,不可按需删除 on-demand 模块 dist:on-demand="true" 📦 运行时按需下载,可卸载释放空间 安装时自动注入 安装时仅占位 SplitInstall 管理 🛈 install-time 模块不可单独卸载;on-demand 模块可由用户通过系统设置主动删除。 配置错误(如同时声明 install-time 与 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 构建与调试要点

  1. 生成 Split APK:使用 ./gradlew bundle 或 Build > Build Bundle(s) / APK(s) > Build APK(s),Android Studio 会自动为每个动态模块生成独立的 .apk 分片。
  2. 本地测试:使用 Android Studio 的 “Run” 按钮默认仅部署 base + install-time 模块。要测试 on-demand 模块,需通过 SplitInstallManager API 或使用 bundletool 安装完整应用。
  3. 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
        }
    }
}

4.8 小结