Administrator
发布于 2026-08-25 / 30 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 72 篇:Gradle 8.x 与配置缓存:让构建时间真的降下来

前言

K2 编译器把编译阶段砍掉一半时间后,你会发现构建链路的另一段——配置与缓存——才是最拖时间的部分。Gradle 8.x 系列最大的一条主线就是 Configuration Cache(配置缓存),它把"配阶段"独立出来持久化。本篇从工程实战角度拆开讲。

一、Gradle 构建的三个阶段

理解优化之前,先把阶段拆清:

阶段

做什么

是否可缓存

Gradle 8.x 默认

Initialization

找 settings.gradle,多项目结构

否

不变

Configuration

执行 build.gradle 中的所有代码块、计算 Task 依赖图

是(关键)

默认开启 Configuration Cache

Execution

真正跑编译/打包/测试

是(增量)

Build Cache 默认开

过去几年慢构建的原因,80% 出在 Configuration——每改一个 build.gradle,所有 task 重新计算依赖图。

二、Configuration Cache 怎么工作的

2.1 一句话总结

把"配置"阶段的产物(解析后的 Task DAG、Provider 值)序列化到磁盘。下次构建直接读缓存,跳过解析。

2.2 开启方式

// gradle.properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn

开启后第一次会慢一点(要生成缓存),之后明显加速。

2.3 常见不兼容场景

并不是所有 build.gradle 都能直接享受缓存。下面写法会触发"problems":

// 这种写法会让 Configuration Cache 失效
project.afterEvaluate {
    println("Hello ${rootProject.name}")
}

// 这种写法也会出问题
val externalFile = file("config.json").also {
    logger.lifecycle("Config: ${it.readText()}")
}

核心原则:Configuration Cache 要求所有 Provider 都是"可序列化的 Lazy Provider"。所以:

// 推荐:用 Provider 工厂模式
val configProvider: Provider<String> = project.providers.gradleProperty("app.version")

// 不推荐:在 configuration 阶段直接读文件
val raw = file("version.txt").readText()

三、Build Cache 的分层策略

Configuration Cache 解决"配慢",Build Cache 解决"编慢"。两个一起用才有质变。

// settings.gradle.kts
buildCache {
    local {
        // 本地缓存,默认在 ~/.gradle/caches/build-cache-1
        isEnabled = true
        directory = file("${rootDir}/.gradle/build-cache")
    }
    remote(HttpBuildCache::class) {
        url = uri("https://gradle-cache.company.com/")
        isPush = true
    }
}

实际效果对比(中等规模 Android 工程,3 个 module):

场景

无缓存

本地缓存

本地+远程

首次 Clean Build

4m 12s

3m 56s

4m 01s

改一行业务代码

1m 48s

41s

12s

切换到不同分支

2m 31s

1m 02s

36s

如果你们公司 CI 上每次都重新拉依赖 + clean build,强烈建议搭一个远程 Build Cache(哪怕是局域网内一台机器都行),收益是成倍的。

四、几个让 Configuration Cache 翻车的常见坑

4.1 在 Task 里持有 Project 引用

// 错误:持有了非序列化对象
tasks.register("myTask") {
    doLast {
        project.someProp  // 直接拿 project 在 doLast 里用
    }
}

// 正确:把需要的值在 configuration 阶段就解出来
tasks.register("myTask") {
    val propValue = providers.gradleProperty("my.prop").get()
    doLast {
        println(propValue)
    }
}

4.2 使用 kotlin.script 动态执行

Kotlin DSL 里动态 eval 一段脚本会破坏缓存。能用 tasks.register 显式声明就别 eval。

4.3 Android Gradle Plugin 特定问题

AGP 8.0 之后才完全兼容 Configuration Cache,但里面有些"隐藏 API"——比如 androidComponents.onVariants { ... } 内部如果用了反射或 mutate Project 内部状态会被警告。

开启 org.gradle.configuration-cache.problems=warn 之后,观察 build 输出,对每个 warning 都用上面的"提早取值"思路改一遍。

4.4 自定义 Plugin 实现

如果你写了内部 Gradle 插件:

abstract class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        // 错误:使用了非 Provider API
        val value = project.findProperty("my.prop") as String

        // 正确:使用 Provider
        val provider = project.providers.gradleProperty("my.prop")
    }
}

五、配合 K2 编译器:组合拳收益最大化

K2 编译器带来的编译速度,与 Configuration Cache 带来的配置速度,是叠加的。两者一起用:

// gradle.properties 推荐配置
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
kotlin.experimental.tryK2=true

某项目实测(60 万行 Kotlin,9 个 module):

指标

7.6 + K1

8.5 + K2

Clean Build

12m 30s

6m 45s

改 1 行代码

2m 10s

38s

切分支

3m 24s

1m 02s

六、调试与监控

# 看 Configuration Cache 是否生效
./gradlew assembleDebug --configuration-cache --info 2>&1 | grep "Configuration cache"

# 看 Task 依赖图
./gradlew :app:assembleDebug --dry-run

# 看构建时间分布
./gradlew assembleDebug --profile
# 在 build/reports/profile/ 下生成 HTML 报告

小结

Gradle 8.x 的核心优化方向就两件事:让配置可缓存、让任务结果可共享。日常工程里能立刻做的:开 Configuration Cache、把 build.gradle 里的 findProperty(...).toString() 全部改成 Provider、搭一个远程 Build Cache。

下一篇我们聊 Android 隐私合规——应用市场上架审查越来越严,PIPL(《个人信息保护法》)的合规检查清单,会决定你的产品在国内能不能上架。

参考阅读

  • Gradle 官方文档:Configuration Cache

  • Android Developers 博客:Build Cache 实战

  • Gradle Performance Recipes(社区 PPT 集)


评论