3、Gradle 组件化配置:多Module项目搭建,组件独立运行与集成模式切换,资源命名规范

好,咱们今天来聊聊组件化落地最核心的一步——Gradle 配置。

说实话,很多同学看组件化的文章,理论都懂,一动手就懵。为什么?因为 Gradle 脚本没写好。我见过太多项目,组件化搞了一半,结果编译脚本乱成一锅粥,最后又退回单模块。嗯,今天我就把我在几个大型项目里踩过的坑、总结出的套路,一次性讲清楚。

3.1 多Module项目结构设计

先说说项目长什么样。我个人习惯,一个组件化项目至少分三层:

  • app壳工程:只负责组装,不写业务代码
  • 业务组件层:比如首页、发现、我的,每个都是一个独立Module
  • 基础组件层:网络库、图片加载、工具类,被所有业务组件依赖

你想想看,如果所有代码都堆在app模块里,那还叫什么组件化?

举个例子,我最近重构的一个项目,目录结构是这样的:

MyApp/
├── app/                          # 壳工程
├── component_home/               # 首页组件
├── component_discover/           # 发现组件
├── component_mine/               # 我的组件
├── lib_network/                  # 网络库
├── lib_image/                    # 图片加载
├── lib_common/                   # 公共工具
└── settings.gradle.kts           # 模块注册文件

这里有个细节:业务组件我统一用 component_ 前缀,基础库用 lib_ 前缀。为什么?因为我在项目中遇到过,有人把业务组件和基础库混在一起,找起来特别费劲。命名规范,从一开始就要定好。

3.2 组件独立运行与集成模式切换

这是组件化最核心的痛点。每个组件在开发时,要能独立运行、独立调试;在集成时,又要能作为library被app壳工程依赖。

怎么做?说白了,就是靠一个开关变量来控制。

我在根项目的 gradle.properties 里加了一个配置:

# 组件独立运行开关
# true = 独立运行模式,false = 集成模式
isRunAlone=false

然后在每个业务组件的 build.gradle.kts 里,根据这个变量动态切换:

if (isRunAlone.toBoolean()) {
    // 独立运行模式:作为application
    apply(plugin = "com.android.application")
} else {
    // 集成模式:作为library
    apply(plugin = "com.android.library")
}

android {
    // 注意:applicationId 只在独立运行时生效
    if (isRunAlone.toBoolean()) {
        defaultConfig {
            applicationId = "com.example.component_home"
        }
    }
    
    // 独立运行时需要单独的AndroidManifest
    sourceSets {
        getByName("main") {
            if (isRunAlone.toBoolean()) {
                manifest.srcFile("src/main/manifest/AndroidManifest.xml")
            } else {
                manifest.srcFile("src/main/AndroidManifest.xml")
            }
        }
    }
}

核心要点:独立运行时,组件需要自己的Application类和启动Activity。集成模式下,这些都要去掉,只保留library的清单文件。

我曾经犯过一个错:独立运行模式下的Application类里,初始化了某个第三方SDK。结果集成到主工程后,那个SDK被初始化了两次,直接崩溃。嗯,后来我学乖了,在独立运行的Application里加了一个判断:

class HomeApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // 只在独立运行时初始化
        if (isRunAlone.toBoolean()) {
            initSDK()
        }
    }
}

3.3 资源命名规范

这个坑,我估计每个搞组件化的人都踩过。资源名冲突,编译时不会报错,运行时直接覆盖。你想想看,两个组件都定义了 R.string.app_name,最后显示的是哪个?完全看运气。

我的解决方案很简单:强制前缀

资源类型 命名规范 示例
布局文件 组件名_功能_描述 home_activity_main.xml
字符串 组件名_功能_描述 home_title_welcome
颜色 组件名_功能_描述 home_bg_primary
图片 组件名_功能_描述 home_ic_avatar

你可能会问:这样命名会不会太长了?

嗯,确实长。但比起排查资源冲突时那种抓狂的感觉,我宁愿多敲几个字符。而且,Android Studio 的代码补全功能,你输入 home_ 就能过滤出所有首页组件的资源,反而更高效。

小技巧:可以在每个组件的 build.gradle.kts 里配置 resourcePrefix,强制约束资源命名:

android {
    resourcePrefix = "home_"
}

这样如果你不小心写了个没有前缀的资源,编译时直接报错。从源头杜绝问题。

3.4 依赖管理与版本统一

组件多了,依赖版本不一致的问题就来了。A组件用OkHttp 4.9,B组件用OkHttp 4.10,最后打包进去两个版本,apk体积变大不说,还可能出兼容性问题。

我的做法是:在根项目建一个 build.gradle.kts 或者 libs.versions.toml,统一管理所有依赖版本。

举个例子,用 libs.versions.toml 的方式:

[versions]
kotlin = "1.9.0"
okhttp = "4.12.0"
retrofit = "2.9.0"

[libraries]
okhttp-core = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

然后在每个组件的 build.gradle.kts 里引用:

dependencies {
    implementation(libs.okhttp.core)
    implementation(libs.retrofit.core)
}

这样,所有组件的依赖版本都统一了。想升级?改一个地方就行。

注意:千万不要在子模块里直接写死版本号。我曾经接手过一个项目,每个模块都写了自己的版本,升级时改了十几个文件,还漏了两个。那感觉,别提多酸爽了。

3.5 总结一下

组件化的 Gradle 配置,说白了就三件事:

  1. 结构清晰:业务组件和基础库分开,命名有规律
  2. 模式切换:用一个开关控制独立运行还是集成,manifest和Application要处理好
  3. 资源规范:强制前缀,统一版本,从源头避免冲突

这些配置看起来琐碎,但都是我在实际项目中一点一点试出来的。你照着做,至少能省掉80%的组件化配置坑。下一章,咱们聊聊组件间的通信——ARouter 的实战用法,那才是真正让组件「活」起来的关键。


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