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

系列文章《Android 工程师的 100 篇进阶笔记》第 11 篇:Gradle 插件化与构建优化:把编译时间砍半

项目小的时候就几个人,编译慢点无所谓。但当模块数超过 20、CI 流水线每天跑几十次、一次全量构建要 10 分钟以上,构建优化就成了真问题。

这篇进入专家篇,讲 Gradle 构建优化和自定义插件。目标不是让你成为 Gradle 专家,而是让你能诊断和解决真实项目里的构建慢问题。


一、为什么你的 Gradle 构建这么慢?

构建慢通常来自这几个方面:

阶段

常见瓶颈

初始化

插件加载、仓库解析、版本冲突

配置

allprojects 写死逻辑、大量 subprojects 遍历

执行

Kotlin 编译、资源打包、Dex 合并、测试运行

后期

上传产物、生成报告

Android 项目最常见的两个慢点:

  1. Kotlin 编译慢:增量编译失效、Annotation Processor 全量跑。

  2. Gradle 配置慢:每次构建都在重复做一些可以缓存的事。


二、诊断工具:先看清楚问题在哪

2.1 Build Scan

./gradlew assembleDebug --scan

Build Scan 会生成一个在线报告,告诉你每个 Task 耗时、依赖、缓存命中情况。定位瓶颈最直观。

2.2 Profile Report

./gradlew assembleDebug --profile

生成 HTML 报告,适合离线查看。

2.3 本地 Build Analyzer

Android Studio 自带 Build Analyzer,每次构建后都能看到每个模块、每个 Task 的耗时。


三、配置阶段优化

3.1 不要用 allprojects 做复杂逻辑

// 不好
allprojects {
    afterEvaluate {
        // 大量逻辑
    }
}

allprojects 会在每个项目评估后执行,配置阶段被拖慢。

优化:把逻辑下沉到 buildSrc 或 convention plugins。

3.2 启用 Configuration Cache

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

Configuration Cache 会把配置阶段的结果缓存起来,下次构建如果脚本没变,可以直接复用。

注意:有些老插件不兼容,需要逐步迁移。

3.3 固定依赖版本

// 不好
implementation("com.squareup.retrofit2:retrofit:2.9.+")

// 好
implementation("com.squareup.retrofit2:retrofit:2.9.0")

动态版本每次都要去仓库解析,拖慢配置阶段。


四、执行阶段优化

4.1 开启 Build Cache

org.gradle.caching=true

Build Cache 能复用其他构建(甚至 CI)已经生成的产物。团队项目一定要配远程 Build Cache(如 Gradle Enterprise)。

4.2 启用 Daemon

org.gradle.daemon=true

Daemon 会保留一个 Gradle 进程,避免每次启动 JVM。

4.3 增量 Annotation Processing

KAPT(Kotlin Annotation Processing Tool)是编译慢的常见元凶。

kapt.incremental.apt=true
kapt.include.compile.classpath=false

如果项目允许,迁移到 KSP(Kotlin Symbol Processing),速度比 KAPT 快很多:

plugins {
    id("com.google.devtools.ksp")
}

4.4 并行编译

org.gradle.parallel=true
org.gradle.workers.max=8

根据机器 CPU 核数调整 workers 数量。


五、模块化与 Gradle 优化

5.1 模块化分层

:app
├── :feature:home
├── :feature:login
├── :feature:profile
├── :core:network
├── :core:database
└── :core:common

每个 feature 模块可以独立编译、独立测试,改动只影响依赖它的模块。

5.2 用 Convention Plugin 统一配置

把重复配置抽成 Convention Plugin:

// buildSrc/src/main/kotlin/android-feature.gradle.kts
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
}

android {
    compileSdk = 34
    defaultConfig {
        minSdk = 24
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

// build.gradle.kts (:feature:home)
plugins {
    id("android-feature")
}

这样 :feature:home 和 :feature:login 的配置完全一致,修改也只需要改一处。


六、自定义 Gradle 插件

当 Convention Plugin 还不够用时,可以写自定义 Gradle 插件给团队提效。

6.1 最小插件示例

在 buildSrc 里创建:

// buildSrc/src/main/kotlin/com/example/CheckPlugin.kt
class CheckPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("checkApiLevel") {
            group = "verification"
            description = "检查 API 级别是否合规"
            doLast {
                project.subprojects {
                    val compileSdk = extensions.findByType<AppExtension>()?.compileSdkVersion
                    println("$name compileSdk: $compileSdk")
                }
            }
        }
    }
}

注册插件:

// buildSrc/src/main/resources/META-INF/gradle-plugins/com.example.check.properties
implementation-class=com.example.CheckPlugin

使用:

plugins {
    id("com.example.check")
}

6.2 自动生成 Build 信息

class BuildInfoPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("generateBuildInfo") {
            doLast {
                val gitCommit = "git rev-parse --short HEAD".runCommand()
                val buildTime = System.currentTimeMillis()

                val outputDir = project.buildDir.resolve("generated/build-info")
                outputDir.mkdirs()
                outputDir.resolve("BuildInfo.kt").writeText(
                    """
                    object BuildInfo {
                        const val GIT_COMMIT = "$gitCommit"
                        const val BUILD_TIME = $buildTime
                    }
                    """.trimIndent()
                )
            }
        }
    }
}

6.3 自定义 Lint 检查

class LintConfigPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("checkForbiddenImports") {
            doLast {
                val forbidden = listOf("android.util.Log", "java.util.Random")
                project.fileTree("src").matching {
                    include("**/*.kt")
                }.forEach { file ->
                    forbidden.forEach { import ->
                        if (file.readText().contains(import)) {
                            throw GradleException("Forbidden import $import in ${file.path}")
                        }
                    }
                }
            }
        }
    }
}

七、实际优化效果参考

一个 30 模块、百万行代码的 Android 项目,经过以下优化后:

优化项

效果

启用 Configuration Cache

配置阶段从 45s → 8s

启用 Remote Build Cache

全量构建从 12min → 4min

KAPT 迁移 KSP

编译时间下降 30-40%

模块化拆分

改动一个 feature 只需编译 3-5 个模块

当然,具体收益取决于项目本身。但方向是对的。


小结

构建优化不是一次性工作,是持续工程:

  1. 先用 Build Scan / Build Analyzer 定位瓶颈。

  2. 配置阶段用 Configuration Cache + 固定版本 + Convention Plugin。

  3. 执行阶段用 Build Cache + KSP + 并行编译。

  4. 自定义插件把团队规范自动化。

  5. 模块化是构建优化的长期基础。


下篇预告

下一篇《第 12 篇:Native 集成:JNI、NDK、C++ 与 Kotlin 的协作》会进入 Android 的 Native 世界:

  1. 什么时候必须用 C/C++。

  2. JNI 基本类型、引用类型、方法签名。

  3. NDK 构建、CMake、性能与包体积权衡。


参考阅读

  • Gradle 官方性能优化指南

  • Android 官方构建优化文档

  • KSP 官方文档

  • 上一篇文章:第 10 篇《Android 网络层架构:Retrofit + OkHttp + 协程的最佳实践》


评论