尽管 Android 使用 Java Toolchain 作为基础,但与纯 Java 项目仍有一些显著差异;这些差异会影响 Task 的可缓存性。对于包含 Kotlin 源代码(并因此使用 kotlin-android 插件)的 Android 项目来说,这一点尤其如此。

消歧

本指南是关于 Gradle 的构建缓存,但您可能也听说过 Android 构建缓存。它们是不同的东西。Android 缓存是 Android 插件中某些 Task 的内部缓存,最终将被移除,转而支持原生的 Gradle 功能。

为什么要使用构建缓存?

构建缓存可以显著提高 Android 项目的构建性能,在许多情况下可以提高 30-40%。Android Gradle 插件提供的许多编译和组装 Task 都是可缓存的,并且随着每次新迭代,越来越多的 Task 也变得可缓存。

更快的 CI 构建

CI 构建尤其受益于构建缓存。典型的 CI 构建以 clean 开始,这意味着现有的构建输出将被删除,并且构成构建的任何 Task 都不会是 UP-TO-DATE。然而,很可能许多 Task 在之前的 CI 构建中已经以完全相同的输入运行过,从而填充了构建缓存;可以安全地重用之前运行的输出,从而显著提高构建性能。

将 CI 构建结果用于本地开发

一天开始工作时,您的第一个 Task 通常是拉取 main 分支,然后运行构建(无论您是否要求,Android Studio 可能都会这样做)。假设所有合并到 main 的更改都在 CI 上构建过(这是一个最佳实践!),那么当您运行当天的第一个本地构建时,可以通过 Gradle 的远程缓存获得比通常更大的益处。CI 已经构建过这个提交了——为什么您还需要重复这项工作呢?

切换分支

在本地开发过程中,一天切换多次分支是很常见的。这会使得增量构建(即 UP-TO-DATE 检查)失效,但可以通过使用本地构建缓存来缓解此问题。您可能在分支 A 上运行构建,这将填充本地缓存。然后您切换到分支 B 进行代码审查、帮助同事或处理开放 PR 的反馈。之后您再切换回分支 A 继续您的原始工作。当您下次构建时,之前在分支 A 上工作时构建的所有输出都可以从缓存中重用,这可能会节省大量时间。

Android Gradle 插件

Android Studio 用户应使用最新的 Android Gradle 插件,以确保兼容性并受益于新版本中的性能改进。

优化构建时,您首先应该始终确保使用的是最新稳定支持版本的 Android Gradle 插件和 Gradle 构建工具。在撰写本文时,它们分别是 3.3.0 和 5.0。这些工具的每个新版本都包含许多性能改进,其中最重要的一项就是针对构建缓存。

Java 和 Kotlin 编译

上文“缓存 Java 项目”中关于讨论在这里同样适用,但需要注意,对于包含 Kotlin 源代码的项目,Kotlin 编译器目前不像 Java 编译器那样支持避免编译(compile avoidance)

注解处理器和 Kotlin

上文针对纯 Java 项目的建议也适用于 Android 项目。但是,如果您将注解处理器(例如 Dagger2 或 Butterknife)与 Kotlin 和 kotlin-kapt 插件结合使用,您应该知道在 Kotlin 1.3.30 之前,kapt 默认不被缓存

您可以选择启用它(推荐做法),通过将以下内容添加到构建脚本中:

build.gradle.kts
pluginManager.withPlugin("kotlin-kapt") {
    configure<KaptExtension> { useBuildCache = true }
}
build.gradle
plugins.withId("kotlin-kapt") {
    kapt.useBuildCache = true
}

单元测试执行

与纯 Java 项目中的单元测试类似,Android 项目中等效的测试 Task (AndroidUnitTest) 自 Android Gradle Plugin 3.6.0 起也变得可缓存。

插桩测试执行(即 Espresso 测试)

Android 插桩测试 (DeviceProviderInstrumentTestTask),通常被称为“Espresso”测试,也是不可缓存的。Google Android 团队也正在努力使这类测试可缓存。请参见此问题

Lint

Android 的 Lint Task 用户深知使用它所付出的沉重性能代价,但也知道它对于发现 Android 项目中的常见问题不可或缺。目前,此 Task 不可缓存。计划在 Android Gradle Plugin 3.5 发布后使其可缓存。这也是始终使用最新版 Android 插件的另一个原因!

Fabric 插件和 Crashlytics

Fabric 插件用于集成 Crashlytics 崩溃报告工具(以及其他工具),它非常流行,但在构建过程中会带来一些严重的性能损失。这是因为需要为应用的每个版本提供唯一的标识符,以便在 Crashlytics 控制面板中进行识别。实际上,Crashlytics 的默认行为是将“每个版本”等同于“每个构建”。这会使得增量构建失效,因为每个构建都是唯一的。出于同样的原因,它还会破坏构建中某些 Task 的可缓存性。这可以通过简单地在“debug”构建中禁用 Crashlytics 来解决。您可以在Crashlytics 文档中找到相关说明。

如果您使用的是 Kotlin DSL,引用文档中描述的修复方法无法直接生效;请参阅下文了解变通方法。

Kotlin DSL

如果您使用的是 Kotlin DSL,引用文档中描述的修复方法无法直接生效;这是由于 Kotlin DSL 与 Fabric 插件不兼容造成的。有一个简单的变通方法可以解决此问题,该方法基于 Kotlin DSL 入门中的此建议

在应用 io.fabric 插件的模块中创建文件 fabric.gradle。此文件(称为脚本插件)应包含以下内容:

fabric.gradle
plugins.withId("com.android.application") { // or "com.android.library"
    android.buildTypes.debug.ext.enableCrashlytics = false
}

然后,在该模块的 build.gradle.kts 文件中应用此脚本插件:

build.gradle.kts
apply(from = "fabric.gradle")