第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 实现流程:从创建到配置的完整步骤

  1. 创建动态模块:在Android Studio中通过 File > New > New Module > Dynamic Feature Module 向导创建,或手动在项目根目录下创建模块目录并配置。
  2. 配置模块的 build.gradle:应用 com.android.dynamic-feature 插件,指定模块依赖基础模块。
  3. 声明分发条件:在模块的 AndroidManifest.xml 的 <dist:module> 和 <dist:delivery> 标签中定义模块类型(按需/条件/随时)及分发属性。
  4. 关联基础模块:在基础模块(app)的 build.gradle 的 android.dynamicFeatures 中添加模块名。
  5. 打包与验证:使用 ./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)不支持通过 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 部署步骤

  1. 在Google Play Console中创建应用,开启“高级功能”中的“动态功能模块”。
  2. 上传包含动态模块的App Bundle(.aab)。
  3. 配置发布轨道(内部测试/封闭测试/正式发布)。
  4. 确保签署证书的密钥与测试设备上安装的应用签名一致。
  5. 使用测试账号安装应用,触发模块下载验证。

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,避免每次启动都重复查询。
动态模块创建与分发条件决策流程 动态模块创建与分发条件决策流程 ① 创建 Dynamic Feature Module ② 配置模块 build.gradle ③ 选择分发类型(Delivery Type) 按需 <on-demand> 安装时 <install-time> 条件 <conditional> ④ 编写 AndroidManifest 分发标签 ⑤ 构建App Bundle & 验证拆分APK 分发条件决定 下载时机与生命周期