10、多模块项目:多项目结构、settings.gradle配置、项目间依赖、统一配置与allprojects/subprojects
说实话,我刚开始用 Gradle 那会儿,所有代码都塞在一个模块里。项目小的时候还行,一旦业务复杂起来,那个 build.gradle 文件能写到上千行。每次构建都慢得像蜗牛,改一行代码要等半分钟才能看到效果。后来我接手了一个真正的大型项目,才意识到——不分模块,你根本没法好好干活。
多模块项目,说白了就是把一个大项目拆成多个小项目。每个小项目有自己的职责,可以独立构建、独立测试。你想想看,这样是不是清爽多了?
10.1 多项目结构长什么样?
一个典型的多模块项目,目录结构大概是这样的:
my-project/
├── settings.gradle
├── build.gradle # 根项目的构建文件
├── app/
│ ├── build.gradle
│ └── src/
├── common/
│ ├── build.gradle
│ └── src/
├── api/
│ ├── build.gradle
│ └── src/
└── utils/
├── build.gradle
└── src/
根目录下有一个 settings.gradle,它负责告诉 Gradle:这个项目有哪些子模块。每个子模块都有自己的 build.gradle,可以独立配置依赖、插件、任务。
我个人习惯把子模块按功能分层:
- common:公共工具类、常量、异常定义
- api:对外暴露的接口定义
- utils:一些辅助功能,比如文件处理、加密
- app:主应用模块,依赖上面所有模块
这样分层的好处是,依赖关系清晰,不会出现循环依赖这种让人头疼的问题。
10.2 settings.gradle 配置
这个文件是 Gradle 多模块项目的入口。没有它,Gradle 根本不知道有哪些子模块。
最简单的写法:
rootProject.name = 'my-project'
include 'app'
include 'common'
include 'api'
include 'utils'
嗯,这里要注意:include 后面的字符串是子模块的路径,相对于根目录。如果子模块在子目录里,比如 libs/security,那就写成:
include 'libs:security'
我曾经踩过一个坑:子模块名称和目录名称不一致。Gradle 默认会用目录名作为模块名,但如果你在 settings.gradle 里显式指定了名称,那就以你指定的为准。比如:
include ':my-app'
project(':my-app').projectDir = file('app')
这样,模块名叫 my-app,但实际代码在 app/ 目录下。说实话,这种写法容易让人迷惑,我建议尽量保持名称一致。
file('modules').eachDir 的方式批量 include,省得一个个写。
10.3 项目间依赖
子模块之间怎么互相依赖?很简单,在 build.gradle 里用 project() 语法:
// app/build.gradle
dependencies {
implementation project(':common')
implementation project(':api')
implementation project(':utils')
}
这样,app 模块就能使用 common、api、utils 里的代码了。
为什么用 implementation 而不是 api?
- implementation:依赖只在当前模块内部可见,外部模块看不到。编译更快,推荐优先使用。
- api:依赖会透传给外部模块。比如
common依赖了某个库,如果app也需要这个库,那common就得用api声明。
我在项目中遇到过一个问题:A 模块依赖 B 模块,B 模块依赖 C 模块。如果 B 用 implementation 依赖 C,那 A 是看不到 C 的。这其实是好事,减少了不必要的耦合。但如果你不小心,可能会在 A 里直接引用 C 的类,然后编译报错——嗯,这时候你就知道该调整依赖关系了。
10.4 统一配置与 allprojects/subprojects
多模块项目里,每个子模块可能都需要配置一些公共的东西,比如:
- Java 版本
- 仓库地址
- 插件版本
- 编码格式
如果每个子模块都写一遍,那跟单模块项目有什么区别?Gradle 提供了两个好东西:allprojects 和 subprojects。
allprojects
作用于所有项目,包括根项目本身。通常用来配置仓库和公共插件:
// 根项目的 build.gradle
allprojects {
repositories {
mavenCentral()
google()
}
}
这样,所有子模块都不用再写 repositories 了。
subprojects
只作用于子模块,不包括根项目。适合配置 Java 版本、编码等:
subprojects {
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}
你看,这样所有子模块都统一用了 Java 17 和 UTF-8 编码。如果某个子模块需要特殊配置,可以在它自己的 build.gradle 里覆盖。
allprojects:根项目 + 所有子模块subprojects:仅子模块
allprojects,把插件和编译配置放 subprojects。
10.5 知识体系总览
下面这张图,帮你理清多模块项目的核心逻辑:
10.6 实战建议
最后,分享几个我在实际项目中的经验:
- 不要过度拆分。模块太多,管理成本会上升。我一般建议 3-6 个模块比较合适。
- 公共依赖统一管理。可以用
ext块定义版本号,或者用buildSrc统一管理依赖版本。 - 注意构建顺序。Gradle 会自动根据依赖关系决定构建顺序,但如果你用了
compileOnly或testOnly,要小心循环依赖。 - 善用
subprojects。把公共配置抽出来,每个子模块只写自己特有的内容,代码量能减少 60% 以上。
多模块项目,说白了就是分而治之。每个模块做好自己的事,通过清晰的依赖关系协作。这样项目大了也不怕,改一个模块不会影响其他模块,构建速度也快得多。
build.gradle 里,结果子模块之间互相看不到依赖,编译报错一堆。后来才明白,每个子模块的依赖应该写在它自己的 build.gradle 里,根项目只负责统一配置。
嗯,多模块项目的内容就这些。记住核心三件事:settings.gradle 声明模块、project() 声明依赖、allprojects/subprojects 统一配置。掌握了这些,你就能轻松驾驭大型 Gradle 项目了。
公众号:蓝海资料掘金营,微信deep3321