Skip to content

工作原理

深入理解 Gradle 的工作原理,有助于编写更高效的构建脚本,排查构建问题。

整体架构

┌─────────────────────────────────────────┐
│              Gradle 构建               │
│                                         │
│  settings.gradle.kts                    │
│       ↓ 初始化阶段                      │
│  build.gradle.kts × N                  │
│       ↓ 配置阶段                        │
│  Task DAG(有向无环图)                  │
│       ↓ 执行阶段                        │
│  执行选中的 Task                        │
└─────────────────────────────────────────┘

三阶段详解

阶段一:初始化(Initialization)

触发时机:每次运行 gradle 命令时

执行内容

  1. 查找 settings.gradle.kts(从当前目录向上查找)
  2. 解析 settings.gradle.kts,确定参与构建的项目列表
  3. 为每个项目创建 Project 实例
kotlin
// settings.gradle.kts 的典型内容
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}

rootProject.name = "my-app"

// 多项目时声明子项目
include("core", "web", "service:auth", "service:order")

阶段二:配置(Configuration)

触发时机:初始化完成后,每次运行 gradle 命令时

执行内容

  1. 按顺序执行每个项目的 build.gradle.kts
  2. 注册所有 Task(不执行,只注册)
  3. 构建 Task 依赖图(DAG)

性能陷阱

配置阶段所有项目的脚本都会执行,即使你只运行一个任务。 避免在配置阶段做耗时操作:

kotlin
// ❌ 错误:配置阶段执行耗时操作
tasks.register("myTask") {
    val result = callExternalAPI()  // 每次构建都调用,很慢
    doLast { println(result) }
}

// ✅ 正确:在执行阶段做耗时操作
tasks.register("myTask") {
    doLast {
        val result = callExternalAPI()  // 只在任务执行时调用
        println(result)
    }
}

阶段三:执行(Execution)

触发时机:配置阶段完成后

执行内容

  1. 根据命令行参数确定要执行的 Task
  2. 按 DAG 顺序执行 Task(依赖的 Task 先执行)
  3. 检查增量构建(跳过输入/输出未变更的 Task)
  4. 查询构建缓存(有缓存则直接使用)

任务有向无环图(DAG)

build
  ├── assemble
  │   └── jar
  │       └── compileJava
  │           └── processResources
  └── check
      └── test
          └── compileTestJava
              └── compileJava
bash
# 查看任务依赖关系
gradle build --dry-run

# 输出示例:
# :compileJava SKIPPED
# :processResources SKIPPED
# :classes SKIPPED
# :jar SKIPPED
# :compileTestJava SKIPPED
# :processTestResources SKIPPED
# :testClasses SKIPPED
# :test SKIPPED
# :check SKIPPED
# :assemble SKIPPED
# :build SKIPPED

增量构建(Incremental Build)

Gradle 通过跟踪 Task 的**输入(Inputs)输出(Outputs)**来决定是否需要重新执行。

kotlin
tasks.register<Copy>("copyDocs") {
    from("src/docs")          // 输入:源目录
    into("$buildDir/docs")   // 输出:目标目录
}

判断逻辑

  • 输入文件没有变化
  • 输出文件存在且与上次构建一致
  • → Task 标记为 UP-TO-DATE,跳过执行
bash
$ gradle copyDocs
> Task :copyDocs

$ gradle copyDocs
> Task :copyDocs UP-TO-DATE   ← 第二次执行,直接跳过

构建缓存(Build Cache)

构建缓存比增量构建更进一步:即使在不同机器上,也可以复用构建结果。

本地缓存:~/.gradle/caches/
远程缓存:Gradle Enterprise / 自定义 HTTP 服务器
bash
# 启用构建缓存
gradle build --build-cache

# 或在 gradle.properties 中永久启用
org.gradle.caching=true

缓存 Key 的计算

  • Task 类型
  • Task 输入(文件内容哈希、属性值)
  • Gradle 版本
  • JVM 版本

配置缓存(Configuration Cache)

Gradle 8+ 支持配置缓存,将配置阶段的结果缓存起来,下次跳过配置阶段。

bash
# 启用配置缓存
gradle build --configuration-cache

# 或在 gradle.properties 中启用
org.gradle.configuration-cache=true
bash
# 第一次(需要配置)
$ gradle build
Calculating task graph as no cached configuration is available for tasks: build
...
Configuration cache entry stored.

# 第二次(使用缓存)
$ gradle build
Reusing configuration cache.
...
BUILD SUCCESSFUL

Gradle Wrapper 机制

Gradle Wrapper 确保所有开发者使用相同版本的 Gradle,无需手动安装。

项目根目录/
├── gradlew              ← Linux/macOS 脚本
├── gradlew.bat          ← Windows 脚本
└── gradle/
    └── wrapper/
        ├── gradle-wrapper.jar       ← Wrapper 启动器
        └── gradle-wrapper.properties ← 版本配置
properties
# gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

工作流程

  1. 执行 ./gradlew build
  2. Wrapper 检查 ~/.gradle/wrapper/dists/ 中是否有指定版本
  3. 没有则自动下载
  4. 使用下载的 Gradle 执行构建

依赖解析过程

声明依赖

查询依赖图(递归解析传递依赖)

版本冲突解决(选择最高版本/指定版本)

查询本地缓存(~/.gradle/caches/)
    ↓ 缓存未命中
从仓库下载(Maven Central 等)

存入本地缓存

放入编译/运行 classpath

守护进程(Gradle Daemon)

Gradle Daemon 是一个后台 JVM 进程,保持 Gradle 热加载状态,大幅加快构建速度。

bash
# 查看守护进程状态
gradle --status

# 停止所有守护进程
gradle --stop

配置

properties
# gradle.properties
org.gradle.daemon=true        # 默认开启
org.gradle.daemon.idletimeout=10800000  # 闲置 3 小时后退出

下一步