4、创建第一个动态功能模块:Module结构、build.gradle配置、feature模块清单
4.1 模块长什么样?先搭架子
在 Android Studio 里,我习惯直接在项目根目录下建一个 features 文件夹(别笑,后来很多项目都跟着我这么干),然后在里面创建动态功能模块。方法很简单:右键项目 → New → Module → Android Dynamic Feature Module。填个名字,比如 feature_video,完事。
创建好之后,你会看到这样一个结构:
app/ // 基础模块(Base)
features/
feature_video/
src/main/
java/ // 代码
res/ // 资源
AndroidManifest.xml // 模块清单
build.gradle // 模块构建脚本
嗯,这里要注意:feature_video 并不是一个独立的 App,它必须挂载在某个“宿主”上。说白了,它就像是 App 的一个可拆卸部件。
4.2 build.gradle 配置:关键一步
模块创建完,第一件事就是配 build.gradle。我个人习惯先看一眼默认生成的,再手动改。
先看 base 模块(app) 的 build.gradle —— 它需要引用所有动态模块:
// app/build.gradle
android {
...
dynamicFeatures = [":features:feature_video"]
}
dependencies {
// 其他依赖...
// 注意:不要在base里直接implementation动态模块的代码
}
再看 feature 模块 的 build.gradle:
// features/feature_video/build.gradle
apply plugin: 'com.android.dynamic-feature'
android {
compileSdk 33
defaultConfig {
minSdk 21
}
}
dependencies {
implementation project(':app') // 必须依赖 base 模块
// 其他 feature 特有的依赖
}
插件用了 com.android.dynamic-feature 而不是 com.android.application 或 com.android.library。这就是告诉 Gradle:“兄弟,我是一块动态砖,哪里需要哪里搬。”
4.3 feature 模块的 AndroidManifest 配置
接下来是清单文件。每个动态模块的 AndroidManifest.xml 里必须写一个 <dist:module> 标签,并指定 split 属性。这个属性的值就是模块的安装标识,将来在代码中请求安装时要用到它。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.feature_video">
<dist:module
dist:instant="false"
dist:title="@string/feature_video_title">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
<application>
<activity android:name=".VideoActivity"
android:exported="false" />
</application>
</manifest>
这里几个关键点:
- dist:on-demand:按需下载,用户不点就不下。
- fusing include="true":允许这个模块在安装包被拆包时,也跟主包融合(即旧设备或没有 Play Core 的情况下还能用)。
- dist:title:模块的显示名称,会在下载通知或界面上看到。
- split 属性:虽然上面没写,但默认
split值就是模块的路径名(例如feature_video)。你也可以显式指定:dist:split="myVideo",这样在请求安装时就要用这个字符串。
split 属性起一个简短、唯一的名字,比如 video 或 ar_core。不要用模块全路径,否则代码里写起来太啰嗦。
4.4 一张图看懂整体结构
我把上面三步串起来,画了一张流程结构图。你看它就像盖房子——先打地基(base),再搭砖块(feature模块),最后用清单告诉系统怎么搬砖。
4.5 配置清单中的坑(我曾经踩过的)
讲几个我亲身经历的坑:
- split 名称冲突:如果你有两个模块都叫
feature_common,那动态交付会直接崩溃。我不止一次看见同事把两个不同 repo 的模块起了同样的 split 名。解决方案:规范命名,比如com.yourcompany.feature.video。 - fusing 不配置:如果
dist:fusing include="false",那么当你把 APK 拆分成多个 split 文件时,这个模块不会打包进基础 APK,旧设备直接无法运行。建议始终设为 true,除非你有非常特殊的场景。 - 清单中缺少
dist:module标签:Gradle aapt 不会报错,但安装时模块不会被识别为动态模块,导致 SplitInstallManager 找不到它。检查清单的最简单方法:编译后查看build/intermediates/manifest下的合并结果。
4.6 小结:一个简单的对照表
| 配置项 | 位置 | 作用 |
|---|---|---|
dynamicFeatures |
app/build.gradle | 声明哪些模块是动态的 |
com.android.dynamic-feature 插件 |
feature/build.gradle | 告诉 Gradle 这是动态模块 |
<dist:module> |
feature/AndroidManifest.xml | 定义模块的分发属性 |
dist:split |
feature/AndroidManifest.xml | 标识模块的唯一名称 |
dist:fusing |
feature/AndroidManifest.xml | 是否允许与主包融合 |
嗯,到现在为止,你的模块骨架就搭起来了。接下来就是往 feature 里填业务代码,再配合 SplitInstallManager 触发下载。这些我们后面几章慢慢聊。