Gradle 构建加速全攻略:从 5 分钟到 30 秒

Gradle 构建加速全攻略:从 5 分钟到 30 秒

"等构建"是 Android 开发者最浪费时间的事情之一。一个中型项目,冷构建动辄 3-5 分钟,热构建也要 30 秒以上,一天下来光等构建就消耗半小时。本文系统梳理 Gradle 构建加速的完整方法论——从配置调优到架构改造,从本地缓存到远程缓存,手把手带你把构建时间压缩到极致。


先诊断,再优化

盲目加配置不会让构建变快,反而可能引入新问题。优化第一步:搞清楚时间花在哪了

开启 Build Scan

Gradle 官方的 Build Scan 是最权威的诊断工具:

// settings.gradle.kts
plugins {
    id("com.gradle.enterprise") version "3.18"
}

gradleEnterprise {
    buildScan {
        termsOfServiceUrl = "https://gradle.com/terms-of-service"
        termsOfServiceAgree = "yes"
        publishAlways()  // 每次构建都生成 scan
    }
}

构建后会在终端输出一个链接,打开后可以看到:

  • 每个任务的执行时间
  • 任务依赖关系图
  • 临界路径(Critical Path)
  • 哪些任务可以并行但没有并行

使用 --profile 本地分析

不想上传数据到 Gradle 服务器?用本地 profile:

./gradlew assembleDebug --profile

构建结束后在 build/reports/profile/ 下生成 HTML 报告,包含:

  • 任务执行时间排序
  • 依赖解析耗时
  • 配置阶段耗时

用 --scan 定位慢任务

# 精确定位哪个任务慢
./gradlew assembleDebug --scan

# 只看特定模块的构建时间
./gradlew :app:assembleDebug --profile

典型的时间分布

一个中型 Android 项目的构建时间通常这样分布:

配置阶段 (Configuration):    15-20%  → 解析所有 build.gradle
依赖解析 (Resolution):       10-15%  → 下载和解析依赖
注解处理 (Annotation Proc):  20-30%  → KSP/KAPT、Room、Hilt 等
编译 (Compilation):          25-35%  → Kotlin/Java 编译
Dex/R8:                     10-15%  → 字节码转换和优化
其他 (Resources/Assets):      5-10%  → 资源处理、打包

优化策略要针对最大头下手。下面按收益从高到低排列。


基础配置优化:零成本提速

这些改动不需要改代码,不需要改架构,只需要调整 Gradle 配置。但效果可能出乎意料。

开启 Gradle 配置缓存

配置缓存(Configuration Cache)是 Gradle 7.x 引入的重磅特性:缓存配置阶段的结果,下次构建直接复用,跳过整个配置阶段。

// gradle.properties
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn  // 先 warn,确保兼容后再改 fail

效果:冷构建配置阶段从 10-15 秒降到 1 秒以内

但有一个前提:你的构建脚本必须是配置缓存兼容的。常见不兼容的情况:

// ❌ 不兼容:在配置阶段访问 Project 实例
tasks.register("myTask") {
    val project = project  // 配置缓存不允许
    doLast { println(project.name) }
}

// ✅ 兼容:通过 Provider 传递
tasks.register("myTask") {
    val projectName = providers.gradleProperty("projectName")
    doLast { println(projectName.get()) }
}

开启并行构建

# gradle.properties
org.gradle.parallel=true        # 允许不同子项目并行构建
org.gradle.workers.max=8        # 最大工作线程数(通常设为 CPU 核心数)

默认情况下 Gradle 是串行构建子项目的。开启并行后,没有依赖关系的模块可以同时编译。

效果:多模块项目构建时间减少 20-40%

增大 Gradle 堆内存

# gradle.properties
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError

关键参数:

  • -Xmx4g:给 Gradle daemon 分配 4GB 堆内存(根据机器内存调整,建议不低于 2GB)
  • -XX:+UseParallelGC:使用并行 GC,降低 GC 暂停
  • -XX:+HeapDumpOnOutOfMemoryError:OOM 时 dump,方便排查

开启构建缓存

# gradle.properties
org.gradle.caching=true

或在命令行:

./gradlew assembleDebug --build-cache

构建缓存会复用之前相同输入的 task 输出。切换分支后再构建时,没改动的模块直接从缓存取结果。

完整的 gradle.properties 优化模板

# === JVM 配置 ===
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# === 并行与缓存 ===
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn

# === Daemon ===
org.gradle.daemon=true          # 确保 daemon 开启(默认就是 true)

# === Android 特定 ===
android.useAndroidX=true
android.nonTransitiveRClass=true   # 非传递 R 类,减少 R 类体积

# === Kotlin ===
kotlin.incremental=true            # Kotlin 增量编译(默认 true)
kotlin.incremental.java=true       # Java 变更也触发增量编译
kotlin.caching.enabled=true

# === 调试 ===
# org.gradle.debug=true           # 只在需要调试时开启
# org.gradle.logging.level=debug  # 太多日志反而慢

依赖优化:砍掉看不见的拖累

检测未使用的依赖

依赖越多,解析越慢,编译也越慢(传递依赖会参与编译 classpath)。

# 使用 gradle-dependency-analyze 插件
./gradlew :app:analyzeDependencies
// build.gradle.kts
plugins {
    id("ca.neitsch.dependencyanalyze") version "1.7.0"
}

手动排查:

# 查看依赖树,找出异常大的传递依赖
./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep "→"

# 查看编译期依赖(这个最影响构建速度)
./gradlew :app:dependencies --configuration debugCompileClasspath

排除不必要的传递依赖

// ❌ 引入了一堆不需要的传递依赖
implementation("com.squareup.retrofit2:retrofit:2.11.0")

// ✅ 精确控制
implementation("com.squareup.retrofit2:retrofit:2.11.0") {
    // 排除不需要的模块
    exclude(group = "com.squareup.okhttp3", module = "okhttp")
}
// 单独引入需要的版本
implementation("com.squareup.okhttp3:okhttp:4.12.0")

使用 compileOnly 替代 implementation

如果一个依赖只在编译期使用(运行时由其他途径提供),用 compileOnly

// Lombok 只在编译期用
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")

// 注解只在编译期用
compileOnly("javax.annotation:javax.annotation-api:1.3.2")

compileOnly 的依赖不会进入运行时 classpath,减少 dex 工作量。

统一依赖版本,避免冲突解析

版本冲突会让 Gradle 花大量时间解析该用哪个版本:

// versionCatalog 统一管理(推荐)
// gradle/libs.versions.toml
[versions]
kotlin = "2.0.21"
compose-bom = "2024.12.01"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }

# 使用
dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(platform(libs.compose.bom))
}

KAPT → KSP:注解处理的质变

KAPT 为什么慢

KAPT(Kotlin Annotation Processing Tool)的工作流程:

Kotlin 源码 → 生成 Java 存根(Stub)→ Java 注解处理器处理 → 生成代码 → Kotlin 编译器编译
                 ↑ 这是瓶颈!

KAPT 必须先把 Kotlin 代码转成 Java 存根,才能让基于 Java 的注解处理器工作。这个"翻译"过程非常慢,而且不支持增量编译。

KSP 的优势

KSP(Kotlin Symbol Processing)直接在 Kotlin 编译器上运行:

Kotlin 源码 → KSP 直接解析 Kotlin AST → 生成代码 → Kotlin 编译器编译
  • 不需要生成 Java 存根
  • 支持增量处理
  • 速度通常是 KAPT 的 2 倍以上

迁移步骤

// 1. 替换插件
// 旧:kotlin("kapt")
// 新:
plugins {
    id("com.google.devtools.ksp") version "2.0.21-1.0.28"
}

// 2. 替换依赖声明
// 旧:kapt("com.google.dagger:hilt-compiler:2.51.1")
// 新:
ksp("com.google.dagger:hilt-compiler:2.51.1")

// 3. 移除 kapt 配置
// 删除 kapt { ... } 块

已支持 KSP 的主流库

KSP 支持 迁移难度
Room ✅ 2.4+ 简单
Hilt/Dagger ✅ 2.48+ 简单
Moshi ✅ 简单 简单
Epoxy ✅ 5.0+ 中等
Glide ✅ 4.16+ 简单
Lombok ❌ 不支持 无法迁移(换掉 Lombok)

实测效果

项目:~50 个模块,Room + Hilt + Moshi

KAPT 冷构建:3 分 12 秒
KSP  冷构建:1 分 48 秒  (-44%)

KAPT 增量:42 秒
KSP  增量:18 秒  (-57%)

如果只能做一件事,就从 KAPT 迁移到 KSP。


增量编译:只编译改动的部分

Kotlin 增量编译

Kotlin 增量编译默认开启,但有些操作会触发全量编译:

# gradle.properties(确认开启)
kotlin.incremental=true
kotlin.incremental.java=true
kotlin.incremental.js=true       # KMP 项目

会破坏增量编译的操作

  1. 修改 build.gradle.kts 中的编译选项
  2. 修改依赖版本
  3. 修改 Kotlin 编译器插件配置
  4. 修改注解处理器的输入/输出

Kotlin 增量编译的"classpath snapshot"

Gradle 8.x + Kotlin 2.x 使用新的增量编译机制:

旧机制:比较源文件的修改时间
新机制:比较编译产物的 ABI(Application Binary Interface)

好处:改了内部实现但没改 ABI → 下游模块不需要重新编译
// build.gradle.kts
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    compilerOptions {
        // 开启 ABI 增量(Gradle 8.2+ 默认开启)
        freeCompilerArgs.add("-Xabi-gen")
    }
}

资源增量编译

Android 资源处理也有增量支持:

// build.gradle.kts (app)
android {
    buildFeatures {
        // 减少资源合并时间
        renderScript = false    // 不用就关掉
        aidl = false            // 不用就关掉
        buildConfig = true      // 需要就保留
    }
}

模块化:构建加速的架构级方案

为什么模块化能加速构建

单模块项目:改一行代码,整个项目重新编译。

多模块项目:改一个模块的代码,只重新编译该模块 + 依赖它的模块。

单模块:
app (2000 文件) → 改 1 个文件 → 编译 2000 文件

多模块:
:core    (200 文件)
:feature-a (300 文件)  ← 改了这里
:feature-b (300 文件)  ← 不需要重新编译
:app      (200 文件)   ← 只重新编译 :feature-a 和 :app

模块化的正确姿势

按功能拆分,不要按层拆分

❌ 按层拆分(变化时多模块同时改):
:data
:domain
:presentation

✅ 按功能拆分(变化时只改一个模块):
:feature:login
:feature:home
:feature:profile
:core:network
:core:database

避免循环依赖

循环依赖会导致 Gradle 无法确定构建顺序,丧失并行构建的优势:

// ❌ 循环依赖
:feature-a  :feature-b  :feature-a

// ✅ 提取公共模块
:feature-a  :core:common  :feature-b

检测循环依赖:

./gradlew :app:dependencies --configuration debugRuntimeClasspath | grep "↳"

API 模块 vs Implementation 模块

// :core:network 对外暴露的接口
// :core:network-api 模块(只有接口,没有实现)
dependencies {
    api(project(":core:network-api"))      // 对外暴露
    implementation(project(":core:network-impl"))  // 内部实现
}

这样依赖 :core:network-api 的模块不需要因为网络实现变了而重新编译。


远程构建缓存:团队级加速

搭建远程缓存

远程缓存让整个团队共享构建产物——你构建过的模块,同事直接下载,不用重新编译。

方案一:Gradle Build Cache Node

// settings.gradle.kts
buildCache {
    local {
        directory = File(rootDir, ".gradle/build-cache")
        removeUnusedEntriesAfterDays = 30
    }
    remote<HttpBuildCache> {
        url = uri("https://cache.example.com/cache/")
        push = System.getenv("CI") == "true"  // CI 推送,开发者只拉取
        credentials {
            username = System.getenv("CACHE_USER")
            password = System.getenv("CACHE_PASSWORD")
        }
    }
}

方案二:S3 作为缓存后端

remote<HttpBuildCache> {
    url = uri("https://your-bucket.s3.amazonaws.com/cache/")
    push = System.getenv("CI") == "true"
}

方案三:自建 Gradle Enterprise

企业级方案,包含 Build Scan + 远程缓存 + 预测性测试选择:

Gradle Enterprise
├── Build Cache (远程缓存)
├── Build Scan (构建分析)
├── Test Distribution (测试分发)
└── Predictive Test Selection (智能测试选择)

缓存命中率优化

远程缓存只有命中才有用。影响命中率的因素:

因素 说明 优化
任务输入不稳定 包含时间戳、随机数的输入 固定输入,排除不稳定字段
绝对路径 不同机器路径不同 使用相对路径
环境变量 不同机器环境不同 @Input 声明关键环境变量
原生代码 不同 CPU 架构产物不同 分架构缓存
// 自定义 Task 的缓存键
abstract class GenerateProtoTask : DefaultTask() {
    @get:InputFile
    @get:PathSensitive(PathSensitivity.RELATIVE)  // 用相对路径
    abstract val protoFile: RegularFileProperty

    @get:Input
    abstract val version: Property<String>  // 声明版本为输入

    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty

    @TaskAction
    fun generate() {
        // ...
    }
}

CI 构建优化

CI 环境的构建优化和本地不同——CI 每次都是干净的,没有增量编译的优势。

分阶段构建

# GitHub Actions 示例
jobs:
  build:
    steps:
      # 1. 只编译,不打包 APK
      - name: Compile
        run: ./gradlew compileDebugKotlin --build-cache

      # 2. 单元测试(可以和编译并行)
      - name: Unit Test
        run: ./gradlew testDebugUnitTest --build-cache

      # 3. 打包 APK(只在需要时执行)
      - name: Assemble
        if: github.ref == 'refs/heads/main'
        run: ./gradlew assembleDebug --build-cache

缓存 Gradle 依赖和构建缓存

# GitHub Actions
- uses: actions/cache@v4
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
      .gradle/build-cache
    key: gradle-${{ hashFiles('**/*.gradle.kts', 'gradle/libs.versions.toml') }}
    restore-keys: |
      gradle-

只构建变更的模块

# 只构建变更影响的模块
CHANGED_MODULES=$(git diff --name-only origin/main...HEAD | \
  grep -oP '^\K[^/]+' | sort -u | \
  awk '{print ":"$1":compileDebugKotlin"}' | tr '\n' ' ')

if [ -n "$CHANGED_MODULES" ]; then
  ./gradlew $CHANGED_MODULES
fi

分布式测试执行

// Gradle Enterprise 的预测性测试选择
gradleEnterprise {
    buildCache {
        local { isEnabled = true }
        remote { url = uri("https://cache.example.com/") }
    }
    predictions {
        // 只运行可能受影响的测试
        enabled = true
    }
}

高级优化:深入编译器

Kotlin 编译器选项调优

// build.gradle.kts
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    compilerOptions {
        jvmTarget.set(JvmTarget.JVM_17)

        freeCompilerArgs.addAll(
            "-Xjvm-default=all",           // 生成默认方法(减少生成类数量)
            "-Xno-optimized-callable-references", // 跳过 callable reference 优化
            "-Xsam-conversions=class",      // SAM 转换策略
        )

        // 增量编译优化
        incremental = true
    }
}

R8 全模式优化

// build.gradle.kts (app)
android {
    buildTypes {
        release {
            isMinifyEnabled = true       // 开启 R8
            isShrinkResources = true     // 资源压缩
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

R8 在 release 构建中减少 dex 数量,间接减少下次构建的 dex 时间。

DexBuilder 增量处理

# gradle.properties
android.enableDexingArtifactTransform.desugaring=true
android.enableParallelDex=true  # 并行 dex(AGP 8.x+ 默认开启)

Android Gradle Plugin 优化

AGP 配置优化

// build.gradle.kts (app)
android {
    // 关闭不需要的构建变体
    variantFilter {
        if (name != "debug" && name != "release") {
            ignore = true
        }
    }

    // 减少打包内容
    packaging {
        resources {
            excludes += setOf(
                "META-INF/DEPENDENCIES",
                "META-INF/LICENSE",
                "META-INF/LICENSE.txt",
                "META-INF/license.txt",
                "META-INF/NOTICE",
                "META-INF/NOTICE.txt",
                "META-INF/notice.txt",
                "META-INF/ASL2.0",
                "META-INF/*.kotlin_module"
            )
        }
    }

    // 关闭不需要的 feature
    buildFeatures {
        buildConfig = true
        viewBinding = true
        dataBinding = false   // 不用就关
        compose = true
        resValues = false     # 不需要动态资源
    }
}

非传递 R 类

# gradle.properties
android.nonTransitiveRClass=true

这个配置让每个模块只包含自己声明的资源 ID,不包含依赖模块的资源 ID。效果:

  • 减少每个模块 R 类的大小
  • 减少资源合并时间
  • 资源变更不会导致所有模块重新编译

按 namespace 分离资源

// build.gradle.kts (library module)
android {
    namespace = "com.example.core.network"  // 明确声明 namespace

    resourcePrefix = "network_"  // 资源前缀,避免冲突
}

实战案例:一个真实项目的优化历程

项目概况

  • 35 个模块(5 个 core + 20 个 feature + 10 个 legacy)
  • Room + Hilt + Moshi + KAPT
  • 冷构建 4 分 50 秒,增量构建 55 秒

优化步骤与效果

第一阶段:零成本配置优化
├── 开启 configuration-cache          → 冷构建 -18 秒
├── 开启 parallel + 调大 JVM          → 冷构建 -25 秒
├── 开启 build-cache                  → 增量 -12 秒
└── android.nonTransitiveRClass=true  → 增量 -5 秒
                                   小计:冷 3 分 47 秒,增量 38 秒

第二阶段:KAPT → KSP 迁移
├── Room KSP 迁移                     → 冷构建 -45 秒
├── Hilt KSP 迁移                     → 冷构建 -30 秒
└── Moshi KSP 迁移                    → 冷构建 -15 秒
                                   小计:冷 2 分 17 秒,增量 18 秒

第三阶段:依赖清理 + 模块拆分
├── 移除 12 个未使用的依赖             → 冷构建 -10 秒
├── 拆分 3 个巨型 feature 模块         → 增量 -6 秒
└── API/Implementation 分离           → 增量 -4 秒
                                   小计:冷 2 分 07 秒,增量 8 秒

第四阶段:远程缓存 + CI 优化
├── 搭建远程缓存                      → CI 冷构建 -60%
├── CI 分阶段构建                     → PR 检查时间 -40%
└── 预测性测试选择                    → 测试时间 -50%

最终结果

                 优化前          优化后         提升
冷构建          4 分 50 秒      2 分 07 秒     -56%
增量构建        55 秒           8 秒           -85%
CI PR 检查      12 分钟         4 分 30 秒     -63%

构建优化检查清单

立即可做(10 分钟内)

  • [ ] 确认 gradle.properties 中的 JVM 参数(-Xmx4g -XX:+UseParallelGC
  • [ ] 开启 org.gradle.parallel=true
  • [ ] 开启 org.gradle.caching=true
  • [ ] 开启 android.nonTransitiveRClass=true
  • [ ] 运行一次 --profile,了解当前基线

本周可做

  • [ ] 开启 org.gradle.configuration-cache=true
  • [ ] 检测并移除未使用的依赖
  • [ ] 关闭不需要的 buildFeatures
  • [ ] 统一版本目录(Version Catalog)

本月可做

  • [ ] KAPT → KSP 迁移
  • [ ] 拆分巨型模块
  • [ ] API/Implementation 分离
  • [ ] 搭建远程构建缓存

长期规划

  • [ ] 全面模块化(按功能拆分)
  • [ ] 引入 Gradle Enterprise
  • [ ] CI 分布式构建
  • [ ] 定期 Build Scan 审查

常见误区

误区一:"升级 Gradle 版本就能变快"

升级确实有帮助,但不是万能的。Gradle 8.x 相比 7.x 在配置缓存和增量编译上有改进,但如果你的构建脚本不兼容配置缓存,升级也用不上。

正确做法:升级后先确认新特性是否生效,用 Build Scan 对比。

误区二:"模块越多越快"

模块化有收益边界。过多的小模块会增加:

  • 配置阶段时间(每个模块都要解析 build.gradle)
  • 依赖解析时间(模块间依赖变多)
  • Kotlin 编译的 classpath 查找时间

经验值:一个模块 50-200 个源文件比较合理。少于 30 个文件的模块考虑合并。

误区三:"开越多线程越快"

org.gradle.workers.max 不是越大越好。太多线程会导致:

  • CPU 上下文切换开销
  • 内存压力增大(每个 worker 有独立类加载器)
  • GC 压力

经验值:设为 CPU 核心数或 CPU 核心数 - 1。

误区四:"远程缓存一定比本地快"

远程缓存受网络延迟影响。在低延迟网络中确实有效,但在高延迟或不稳定网络中,下载缓存可能比本地编译还慢。

正确做法:CI 环境推送缓存,开发者环境设置 push = false 只拉取。


结语

构建优化不是一次性的工作,而是持续的过程。项目的代码在增长,依赖在变化,构建时间也会慢慢膨胀。关键不是一次优化到位,而是建立可观测性和持续优化的习惯

我的建议是:

  1. 先量后改:用 Build Scan 或 --profile 量化当前状态
  2. 先低后高:先做零成本配置优化,再做架构级改造
  3. 先人后机:先优化开发者日常的增量构建,再优化 CI 的冷构建
  4. 定期体检:每月跑一次 Build Scan,监控构建时间趋势

构建时间每减少 10 秒,假设团队 10 人每天构建 20 次,一年就省下了:

10  × 10  × 20  × 250 工作日 = 500,000   138 小时  17 个工作日

这 17 天,够写多少有价值的功能了。


参考资源: - Gradle 官方文档 — Performance Tuning - Android Developers — Reduce build times - 《Gradle in Action》— Benjamin Muschko - Google I/O 2025 — What's new in Android Gradle Plugin - Gradle Build Cache 最佳实践