Skip to content

配置缓存适配

源:Gradle 官方文档 - Configuration Cache

配置缓存通过序列化配置结果并跳过配置阶段,实现50%+的构建速度提升。

配置缓存原理

什么是配置缓存

Gradle 构建生命周期

  1. Initialization(初始化)
  2. Configuration(配置)← 配置缓存跳过这里
  3. Execution(执行)

传统构建流程

bash
$ ./gradlew build
> Configuring project    # 配置阶段(每次都执行)
> Task :app:compile
> Task :app:test
BUILD SUCCESSFUL

配置缓存后

bash
$ ./gradlew build
> Reusing configuration cache.    # 跳过配置!
> Task :app:compile UP-TO-DATE
> Task :app:test UP-TO-DATE
BUILD SUCCESSFUL

性能提升

典型项目

  • 配置阶段:5-15 秒
  • 执行阶段:20-60 秒

开启配置缓存后

  • 配置阶段:< 1 秒(从缓存加载)
  • 执行阶段:20-60 秒(不变)
  • 总提升:20-50%

启用配置缓存

gradle.properties 配置

properties
org.gradle.configuration-cache=true

命令行启用

bash
./gradlew build --configuration-cache

问题报告模式

properties
# 遇到问题时失败(推荐)
org.gradle.configuration-cache.problems=fail

# 或仅警告
org.gradle.configuration-cache.problems=warn

兼容性要求

核心限制

配置缓存要求任务在执行阶段不能访问配置阶段的对象:

禁止访问的对象

  • project
  • rootProject
  • allprojects
  • subprojects
  • parent

为什么有这些限制

原因:配置缓存序列化任务配置,而 project 对象无法被序列化。


常见违规代码

违规 1:在 doLast 中访问 project

❌ 错误代码

kotlin
tasks.register("printName") {
    doLast {
        println(project.name)  // 违规!
    }
}

问题project 是配置对象,无法序列化。

✅ 修复方案

kotlin
tasks.register("printName") {
    val projectName = project.name  // 配置阶段获取
    
    doLast {
        println(projectName)  // 执行阶段使用
    }
}

或使用 Provider

kotlin
abstract class PrintNameTask : DefaultTask() {
    @get:Input
    abstract val projectName: Property<String>
    
    @TaskAction
    fun execute() {
        println(projectName.get())
    }
}

tasks.register<PrintNameTask>("printName") {
    projectName.set(project.name)
}

违规 2:在任务中访问其他 project

❌ 错误代码

kotlin
tasks.register("checkVersion") {
    doLast {
        val coreVersion = project(":core").version  // 违规!
        println(coreVersion)
    }
}

✅ 修复方案

kotlin
tasks.register("checkVersion") {
    val coreVersion = project(":core").version
    
    doLast {
        println(coreVersion)
    }
}

违规 3:使用 buildDir

❌ 错误代码

kotlin
tasks.register("generateFile") {
    doLast {
        val output = File(project.buildDir, "output.txt")  // 违规!
        output.writeText("data")
    }
}

✅ 修复方案

kotlin
abstract class GenerateFileTask : DefaultTask() {
    @get:OutputFile
    abstract val outputFile: RegularFileProperty
    
    @TaskAction
    fun execute() {
        outputFile.get().asFile.writeText("data")
    }
}

tasks.register<GenerateFileTask>("generateFile") {
    outputFile.set(layout.buildDirectory.file("output.txt"))
}

违规 4:配置阶段执行命令

❌ 错误代码

kotlin
// 配置阶段执行
val gitHash = "git rev-parse HEAD".execute().text

tasks.register("printHash") {
    doLast {
        println(gitHash)
    }
}

✅ 修复方案

kotlin
tasks.register("printHash") {
    val gitHash = providers.exec {
        commandLine("git", "rev-parse", "HEAD")
    }.standardOutput.asText
    
    doLast {
        println(gitHash.get())
    }
}

Lazy API 适配

使用 Provider API

核心原则:使用 Provider 延迟计算。

传统方式 vs Lazy API

kotlin
// ❌ 传统方式
val version = project.version.toString()

// ✅ Lazy API
val version = providers.provider { project.version.toString() }

Property 类型

常用 Property

kotlin
abstract val stringProp: Property<String>
abstract val intProp: Property<Int>
abstract val fileProp: RegularFileProperty
abstract val dirProp: DirectoryProperty
abstract val listProp: ListProperty<String>
abstract val mapProp: MapProperty<String, String>

示例:完全兼容的任务

kotlin
@CacheableTask
abstract class ProcessTask : DefaultTask() {
    @get:InputFile
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val inputFile: RegularFileProperty
    
    @get:Input
    abstract val version: Property<String>
    
    @get:OutputFile
    abstract val outputFile: RegularFileProperty
    
    @TaskAction
    fun execute() {
        val input = inputFile.get().asFile.readText()
        val result = "$input (version: ${version.get()})"
        outputFile.get().asFile.writeText(result)
    }
}

tasks.register<ProcessTask>("process") {
    inputFile.set(file("input.txt"))
    version.set(project.version.toString())
    outputFile.set(layout.buildDirectory.file("output.txt"))
}

诊断和修复

运行诊断

bash
./gradlew build --configuration-cache

输出示例

Configuration cache entry stored.

3 problems were found storing the configuration cache.
- Task :app:myTask of type MyTask: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject'
- Task :app:otherTask: invocation of 'Project.getBuildDir()' at execution time

See the complete report at file:///path/to/report.html

查看详细报告

报告位置:build/reports/configuration-cache/<hash>/configuration-cache-report.html

报告内容

  • 问题列表
  • 问题位置(文件和行号)
  • 建议修复方案

插件兼容性

检查插件兼容性

bash
./gradlew help --configuration-cache

常见插件兼容性

插件兼容性备注
Android Gradle Plugin 8.0+完全支持
Kotlin Plugin 1.8+完全支持
Hilt需要最新版本
RoomKSP 版本支持
Firebase⚠️部分版本支持

不兼容插件的处理

临时禁用配置缓存

kotlin
configurations.all {
    if (!gradle.startParameter.isConfigurationCacheRequested) {
        // 仅在未启用配置缓存时执行
    }
}

配置缓存与构建缓存

区别对比

特性配置缓存构建缓存
跳过阶段ConfigurationExecution
缓存内容任务配置任务输出
速度提升20-50%30-80%
兼容性要求
推荐组合✅ 同时使用✅ 同时使用

同时启用

properties
# gradle.properties
org.gradle.configuration-cache=true
org.gradle.caching=true
org.gradle.parallel=true

效果叠加

  • 配置缓存:跳过配置阶段
  • 构建缓存:复用任务输出
  • 并行构建:同时执行多个任务
  • 总提升:50-90%

实战案例

案例1:Android 项目适配

before

kotlin
// build.gradle.kts
val flavor = project.findProperty("flavor") as? String ?: "dev"

android {
    productFlavors {
        create(flavor) {
            // 配置
        }
    }
}

after

kotlin
val flavor = providers.gradleProperty("flavor")
    .orElse("dev")

android {
    productFlavors {
        create(flavor.get()) {
            // 配置
        }
    }
}

案例2:自定义任务适配

before

kotlin
tasks.register("generateConfig") {
    doLast {
        val env = project.findProperty("env") as? String ?: "dev"
        File(project.buildDir, "config.json").writeText("""{"env": "$env"}""")
    }
}

after

kotlin
abstract class GenerateConfigTask : DefaultTask() {
    @get:Input
    abstract val environment: Property<String>
    
    @get:OutputFile
    abstract val configFile: RegularFileProperty
    
    @TaskAction
    fun execute() {
        val config = """{"env": "${environment.get()}"}"""
        configFile.get().asFile.writeText(config)
    }
}

tasks.register<GenerateConfigTask>("generateConfig") {
    environment.set(
        providers.gradleProperty("env").orElse("dev")
    )
    configFile.set(layout.buildDirectory.file("config.json"))
}

案例3:Git 信息集成

before

kotlin
val gitHash = "git rev-parse HEAD".execute().text.trim()

android {
    defaultConfig {
        buildConfigField("String", "GIT_HASH", "\"$gitHash\"")
    }
}

after

kotlin
val gitHash = providers.exec {
    commandLine("git", "rev-parse", "HEAD")
}.standardOutput.asText.map { it.trim() }

android {
    defaultConfig {
        buildConfigField("String", "GIT_HASH", "\"${gitHash.get()}\"")
    }
}

最佳实践

使用 Lazy API

  • Provider/Property 代替直接值
  • exec Provider 执行命令
  • fileContents Provider 读取文件

避免访问 project

  • 配置阶段提取值
  • 使用 layout API
  • 使用 providers API

声明输入输出

  • @Input/@Output 注解
  • 支持增量构建
  • 支持缓存

测试兼容性

bash
./gradlew build --configuration-cache --configuration-cache-problems=fail

CI 强制启用

properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=fail

监控效果

  • 使用 Build Scan
  • 查看配置时间
  • 对比前后性能