1. Gradle 多模块项目结构认知:模块划分、settings.gradle 配置、根项目与子项目关系

多模块构建是 Gradle 管理大型工程的核心能力。通过将代码按职责、复用度与变更频率拆分成独立模块,既能提升并行编译效率,又能使依赖关系清晰可控。本章聚焦多模块项目的基础结构认知,为后续编译提速方案奠定空间布局基础。

1.1 模块划分原则

合理的模块划分直接影响构建生命周期与缓存命中率。常见划分策略包括:

  • 按功能域(Feature)拆分:每个业务功能独立模块,如 login、search、payment。
  • 按架构层(Layer)拆分:domain(纯业务)、data(数据源)、presentation(UI)。
  • 按复用粒度拆分:common(基础工具库)、core(核心抽象)、lib(可发布组件)。
关键指标:理想状态下,模块间的单向依赖应占 90% 以上,避免循环依赖;模块变更频率越低,越适合放在依赖链上游。

1.2 settings.gradle 配置:模块清单与路径

settings.gradle[.kts] 是 Gradle 找到子模块的唯一入口。每个参与构建的子模块都必须在此文件中通过 include 声明。其配置语法决定了模块在文件系统中的查找逻辑与在构建中的标识名称。

基础语法示例

// settings.gradle.kts
rootProject.name = "my-multi-project"

// 扁平结构:include "模块名",默认路径与模块名相同
include("app")
include("lib")
include("common")

// 分层结构:include 完整路径,并指定逻辑名称
include(":data:api")
include(":data:impl")
project(":data:api").projectDir = file("data/api")
project(":data:impl").projectDir = file("data/impl")
配置形式模块标识(Project Path)默认源码目录适用场景
include(":app"):app./app简单扁平结构
include(":common:utils"):common:utils./common/utils分层仓库,逻辑分组
include(":lib:network") + projectDir:lib:network自定义路径模块路径与逻辑名不一致时
常见错误:路径冲突与名称不唯一
- 两个模块使用相同的 Project Path(如 :app 被 include 两次)导致构建失败。
- include 字符串使用 / 而非 :,Gradle 无法解析。
- 分层 include 时,父目录模块(如 :data)若未显式 include,其本身不会生成独立 project 实例。

1.3 根项目与子项目关系

根项目(Root Project)的 build.gradle.kts 通常仅用于聚合配置,不生产构建产物。子项目(Subproject)拥有独立的构建脚本,可以被独立编译、测试和发布。多模块间的配置共享通过以下机制实现:

  • allprojects:作用于根项目 + 所有子项目,适合通用插件或仓库。
  • subprojects:仅作用于所有子项目,适合常见编译配置(如 Java 版本、lint 配置)。
  • 配置注入:在根项目中用 project(":moduleA") { ... } 为特定子项目追加配置。

典型根构建脚本片段

// 根项目 build.gradle.kts
plugins {
    java
}

allprojects {
    repositories {
        mavenCentral()
    }
}

subprojects {
    apply(plugin = "java-library")
    java {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    tasks.withType<Test> {
        useJUnitPlatform()
        maxParallelForks = Runtime.getRuntime().availableProcessors()
    }
}

// 为特定子项目单独配置
project(":common") {
    dependencies {
        implementation("org.jetbrains:annotations:24.0.0")
    }
}

子项目构建脚本示例(:lib)

// lib/build.gradle.kts
plugins {
    `java-library`
    `maven-publish`
}

dependencies {
    api(project(":common"))      // 对外暴露 common 的 API
    implementation("com.google.guava:guava:32.1.3-jre")
}

publishing {
    publications {
        register<MavenPublication>("release") {
            from(components["java"])
        }
    }
}

1.4 多模块项目结构总览

下图以典型 Android/Java 多模块项目为例,展示根项目、settings.gradle 与各模块之间的层次关系及依赖流向。

Gradle 多模块项目结构 Gradle 多模块项目结构示意 根项目 (Root) settings.gradle.kts ▸ 子模块区域 (subprojects) :app (应用模块) :lib (可发布库) :common (公共工具) :data:impl (数据实现) :data:api (数据接口) 说明: 根项目通过 settings.gradle.kts 收集所有 include 的子模块; 虚线箭头表示项目依赖关系(compile-time),依赖链越短并行度越高。

1.5 模块依赖的生命周期影响

多模块的依赖拓扑决定了 Gradle 的任务执行顺序与缓存策略。下表对比常见依赖关系对编译性能的影响:

依赖类型示例对构建的影响提速建议
层级依赖app → lib → common修改 common 会触发 lib 和 app 重新编译common 保持高度稳定,减少频繁变动
对等依赖data:api ← data:impl修改接口定义会波及其所有实现模块接口模块尽量只包含纯抽象,依赖具体实现用运行时注入
循环依赖模块A → 模块B → 模块AGradle 编译报错,不允许存在通过接口提取或事件总线解耦
性能关键点:Gradle 的 并行构建(--parallel)在多模块项目中效果显著。每个子项目可用独立线程编译,但依赖链上游的模块必须优先完成。将变更频率高的模块放在依赖树叶子节点,可以最大化并行收益。

1.6 调试与验证

在开始优化前,需确保多模块结构配置正确。以下命令可用于快速诊断:

# 列出所有已配置的 project 及路径
gradle projects

# 查看具体模块的依赖树
gradle :app:dependencies --configuration implementation

# 验证 settings.gradle 语法(只解析不执行)
gradle --init-script check-settings.gradle.kts help
避坑指南:
- 子模块的 group 和 version 若未在根项目中统一,可能导致发布到本地仓库时版本混乱。
- 使用 kotlin-dsl 时,settings.gradle.kts 文件内不建议写复杂逻辑,仅保留 include 与 projectDir 设置。
- 每个子模块的 build.gradle.kts 必须独立声明所需的插件,不能依赖根项目的插件传递。

掌握了模块划分思想、settings.gradle 的写法以及根/子项目配置关系后,下一阶段将深入 Gradle 的配置阶段、执行阶段与缓存机制,为后续的编译提速方案提供理论支撑。