20、模块化与组件化:AOSP模块化设计、组件化编译、动态功能模块
说到Android构建系统,有个话题绕不开——模块化与组件化。我刚开始接触AOSP时,面对那动辄几十G的源码,说实话有点懵。整个系统就像一座巨大的乐高城堡,但你不知道哪些砖块是干嘛的。
后来我慢慢理解了,Google在设计AOSP时,骨子里就透着模块化的基因。你想想看,一个系统要适配上千种设备,如果不拆开,那维护成本得有多高?
模块化设计的底层逻辑
AOSP的模块化,说白了就是「高内聚、低耦合」这套老话。但具体怎么做的?我拿实际例子给你讲。
每个模块都有一个Android.bp或Android.mk文件。这个文件定义了模块的名字、依赖、编译方式等。比如你写一个系统服务,它可能长这样:
// frameworks/base/services/core/Android.bp
java_library {
name: "services.core",
srcs: ["src/**/*.java"],
libs: [
"framework",
"servicesspecific",
],
static_libs: [
"androidx.annotation",
],
installable: true,
}
这里有个关键点——name就是模块的唯一标识。我在项目中遇到过一个问题:两个模块取了相同的名字,结果编译时疯狂报冲突。嗯,从那以后我养成了习惯,命名时一定加上模块路径前缀。
组件化编译:让构建飞起来
组件化编译,说白了就是「只编译你改动的部分」。AOSP的构建系统非常聪明,它会根据依赖关系图,只重新编译受影响的模块。
我记得有一次,我改了一个底层库的接口。按以前的习惯,我准备去泡杯咖啡等全量编译。结果同事告诉我:「你试试mma命令」。我试了一下,只编译了那个库和依赖它的几个模块,全程不到两分钟。
常用的组件化编译命令:
| 命令 | 作用 | 适用场景 |
|---|---|---|
mma |
编译当前模块及其依赖 | 修改了单个模块 |
mmma |
编译指定目录下的模块 | 修改了多个相关模块 |
make |
全量编译 | 首次编译或大版本升级 |
m |
从源码根目录编译指定模块 | 快速验证单个模块 |
mma,提交前跑一次m确保没有破坏其他模块。这样既快又稳。
动态功能模块:按需加载的艺术
动态功能模块是Android App Bundle带来的好东西。它允许你把应用拆成多个模块,用户需要时才下载。这对大型应用来说简直是福音。
你想想看,一个电商App,用户可能只用到首页和搜索功能。你把直播、游戏、社区这些模块做成动态的,用户想用的时候再下载,既节省了安装包体积,又提升了首次启动速度。
配置方式也很简单,在build.gradle里声明:
// app/build.gradle
android {
dynamicFeatures = [":feature_live", ":feature_game"]
}
// feature_live/build.gradle
apply plugin: 'com.android.dynamic-feature'
android {
...
}
dependencies {
implementation project(':app')
}
这里要注意一个坑——我曾经把动态模块的minSdkVersion设得比主模块高,结果在低版本设备上直接崩溃。嗯,动态模块的minSdkVersion必须和主模块一致,或者更高,但不能低。
dist:install-time属性要慎重设置。设成ondemand表示按需下载,设成instant表示即时使用。我见过有人把核心功能设成ondemand,结果用户打开App发现白屏——因为核心模块还没下载完。
模块化与组件化的关系
这两个概念经常被混用,但在我理解里,它们有本质区别:
- 模块化:关注的是代码的组织方式。把系统拆成独立的模块,每个模块有清晰的接口和边界。
- 组件化:关注的是编译和运行时的组合方式。把模块组装成最终产品,可以按需选择。
打个比方,模块化就像你买了一堆乐高零件,每个零件都是独立的。组件化就像你按照图纸,把零件拼成一辆车、一栋房子。同一个零件,可以出现在不同的组件里。
在AOSP里,模块化体现在每个Android.bp文件定义的独立单元。组件化体现在PRODUCT_PACKAGES变量里,你可以选择哪些模块打包进系统镜像。
实战经验总结
做了这么多年系统开发,我总结了几条模块化设计的原则:
- 接口先行:先定义模块间的通信接口,再实现内部逻辑。我见过太多项目,模块之间直接调用内部类,结果一改就崩。
- 依赖单向:模块依赖关系不能有环。AOSP的构建系统会检测循环依赖,一旦发现直接报错。我曾经为了省事,让A依赖B,B又依赖A,结果编译时被卡了半小时。
- 版本管理:每个模块最好有独立的版本号。这样升级时能清楚知道哪些模块变了。AOSP里用
LOCAL_MODULE_VERSION来控制。 - 测试独立:每个模块都要能独立测试。我习惯在每个模块目录下建一个
tests/文件夹,专门放单元测试。
Android.bp,把依赖关系理清楚,再开始写代码。这样能避免后期因为依赖问题大改。
模块化和组件化不是银弹,但它们确实是管理大型项目的有效手段。AOSP能支撑上千种设备,靠的就是这套设计哲学。你想想看,如果没有模块化,每次修改都要全量编译,那开发效率得有多低?
嗯,今天就聊到这里。记住一句话:好的模块化设计,是让每个模块都觉得自己是独立的,但又离不开整体。
公众号:蓝海资料掘金营,微信deep3321