20、模块化与组件化:AOSP模块化设计、组件化编译、动态功能模块

说到Android构建系统,有个话题绕不开——模块化与组件化。我刚开始接触AOSP时,面对那动辄几十G的源码,说实话有点懵。整个系统就像一座巨大的乐高城堡,但你不知道哪些砖块是干嘛的。

后来我慢慢理解了,Google在设计AOSP时,骨子里就透着模块化的基因。你想想看,一个系统要适配上千种设备,如果不拆开,那维护成本得有多高?

模块化设计的底层逻辑

AOSP的模块化,说白了就是「高内聚、低耦合」这套老话。但具体怎么做的?我拿实际例子给你讲。

每个模块都有一个Android.bpAndroid.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就是模块的唯一标识。我在项目中遇到过一个问题:两个模块取了相同的名字,结果编译时疯狂报冲突。嗯,从那以后我养成了习惯,命名时一定加上模块路径前缀。

核心原则:每个模块只做一件事,并且做好。比如Settings模块只负责UI,不要在里面塞网络管理的逻辑。

组件化编译:让构建飞起来

组件化编译,说白了就是「只编译你改动的部分」。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模块化与组件化架构 模块化层(代码组织) 系统服务模块 UI框架模块 硬件抽象模块 应用框架模块 组件化层(编译组合) PRODUCT_PACKAGES 配置 Android.bp 依赖声明 动态功能模块配置 最终产物:系统镜像 / APK 按需组合,灵活适配不同设备

实战经验总结

做了这么多年系统开发,我总结了几条模块化设计的原则:

  1. 接口先行:先定义模块间的通信接口,再实现内部逻辑。我见过太多项目,模块之间直接调用内部类,结果一改就崩。
  2. 依赖单向:模块依赖关系不能有环。AOSP的构建系统会检测循环依赖,一旦发现直接报错。我曾经为了省事,让A依赖B,B又依赖A,结果编译时被卡了半小时。
  3. 版本管理:每个模块最好有独立的版本号。这样升级时能清楚知道哪些模块变了。AOSP里用LOCAL_MODULE_VERSION来控制。
  4. 测试独立:每个模块都要能独立测试。我习惯在每个模块目录下建一个tests/文件夹,专门放单元测试。
我的习惯:每次新建模块时,先写一个空的Android.bp,把依赖关系理清楚,再开始写代码。这样能避免后期因为依赖问题大改。

模块化和组件化不是银弹,但它们确实是管理大型项目的有效手段。AOSP能支撑上千种设备,靠的就是这套设计哲学。你想想看,如果没有模块化,每次修改都要全量编译,那开发效率得有多低?

嗯,今天就聊到这里。记住一句话:好的模块化设计,是让每个模块都觉得自己是独立的,但又离不开整体。


公众号:蓝海资料掘金营,微信deep3321