构建性能对于生产力至关重要。构建完成所需的时间越长,就越有可能中断你的开发流程。构建每天运行多次,因此即使是很小的等待时间也会累积起来。持续集成 (CI) 构建也是如此:它们花费的时间越少,你就能更快地对新问题做出反应,并且可以更频繁地进行实验。

所有这些都意味着,投入一些时间和精力来尽可能地加快构建速度是值得的。本节提供了一些加快构建速度的方法。此外,你还将找到关于什么导致构建性能下降以及如何避免它的详细信息。

想要更快的 Gradle 构建吗?在此注册我们的构建缓存培训课程,了解 Develocity 如何将构建速度提高高达 90%。

检查你的构建

在你进行任何更改之前,使用构建扫描或性能分析报告检查你的构建。正确的构建检查可以帮助你理解

  • 构建你的项目需要多长时间

  • 构建的哪些部分很慢

检查提供了一个比较点,以更好地理解此页面上推荐的更改的影响。

为了最好地利用此页面

  1. 检查你的构建。

  2. 做出更改。

  3. 再次检查你的构建。

如果更改改善了构建时间,请使其永久生效。如果你没有看到改进,请删除更改并尝试另一个。

更新版本

Gradle

Gradle 团队不断改进 Gradle 构建的性能。如果你正在使用旧版本的 Gradle,你将错过这项工作带来的好处。跟上 Gradle 版本升级的风险很低,因为 Gradle 团队确保了 Gradle 次要版本之间的向后兼容性。保持最新状态还可以使过渡到下一个主要版本更容易,因为你将获得早期的弃用警告。

Java

Gradle 运行在 Java 虚拟机 (JVM) 上。Java 性能改进通常有利于 Gradle。为了获得最佳的 Gradle 性能,请使用最新版本的 Java。

插件

插件编写者不断改进其插件的性能。如果你正在使用旧版本的插件,你将错过这项工作带来的好处。特别是 Android、Java 和 Kotlin 插件,它们可以显著影响构建性能。更新到这些插件的最新版本以获得性能改进。

启用并行执行

大多数项目由多个子项目组成。通常,其中一些子项目彼此独立;也就是说,它们不共享状态。然而,默认情况下,Gradle 一次只运行一个 task。要并行执行属于不同子项目的 task,请使用 parallel 标志

$ gradle <task> --parallel

要默认并行执行项目 task,请将以下设置添加到项目根目录或 Gradle home 目录下的 gradle.properties 文件中

gradle.properties
org.gradle.parallel=true

并行构建可以显著提高构建时间;提高多少取决于你的项目结构以及子项目之间有多少依赖关系。构建的执行时间如果由单个子项目主导,则不会获得太多好处。具有大量子项目间依赖关系的项目也不会。但大多数多子项目构建都会看到构建时间的减少。

使用构建扫描可视化并行性

构建扫描为你提供 task 执行的可视化时间线。在以下示例构建中,你可以看到构建开始和结束时的长时间运行的 task

parallel task slow
图 1. 并行执行中的瓶颈

调整构建配置以尽早并行运行两个慢速 task,可以将总构建时间从 8 秒减少到 5 秒

parallel task fast
图 2. 优化的并行执行

重新启用 Gradle Daemon

Gradle Daemon 通过以下方式减少构建时间:

  • 在构建之间缓存项目信息

  • 在后台运行,因此每个 Gradle 构建都不必等待 JVM 启动

  • 从 JVM 中的持续运行时优化中获益

  • 监听文件系统以精确计算在运行构建之前需要重建的内容

Gradle 默认启用 Daemon,但某些构建会覆盖此首选项。如果你的构建禁用 Daemon,则启用 Daemon 可能会带来显著的性能提升。

你可以在构建时使用 daemon 标志启用 Daemon

$ gradle <task> --daemon

要在旧版本 Gradle 中默认启用 Daemon,请将以下设置添加到项目根目录或 Gradle home 目录下的 gradle.properties 文件中

gradle.properties
org.gradle.daemon=true

在开发者机器上,你应该会看到显著的性能提升。在 CI 机器上,长期存在的 agent 会从 Daemon 中获益。但生命周期短的机器不会获得太多好处。在 Gradle 3.0 及更高版本中,Daemon 会在内存压力下自动关闭,因此始终可以安全地保持 Daemon 启用状态。

启用配置缓存

此功能有以下限制:

  • 配置缓存不支持所有核心 Gradle 插件功能。完全支持正在进行中。

  • 你的构建和你依赖的插件可能需要更改才能满足要求

  • IDE 导入和同步不使用配置缓存。

你可以通过启用配置缓存来缓存配置阶段的结果。当构建配置输入在构建之间保持不变时,配置缓存允许 Gradle 完全跳过配置阶段。

构建配置输入包括:

  • Init 脚本

  • Settings 脚本

  • Build 脚本

  • 配置阶段使用的系统属性

  • 配置阶段使用的 Gradle 属性

  • 配置阶段使用的环境变量

  • 使用值提供器(如 provider)访问的配置文件

  • buildSrc 输入,包括构建配置输入和源文件

默认情况下,Gradle 不使用配置缓存。要在构建时启用配置缓存,请使用 configuration-cache 标志

$ gradle <task> --configuration-cache

要默认启用配置缓存,请将以下设置添加到项目根目录或 Gradle home 目录下的 gradle.properties 文件中

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

有关配置缓存的更多信息,请查看配置缓存文档

配置缓存的其他好处

配置缓存也带来了其他好处。启用后,Gradle:

  • 并行执行所有 task,即使是同一子项目中的 task。

  • 缓存依赖解析结果。

为自定义 task 启用增量构建

增量构建是一种 Gradle 优化,它会跳过运行之前使用相同输入执行过的 task。如果 task 的输入和输出自上次执行以来没有更改,Gradle 将跳过该 task。

Gradle 提供的大多数内置 task 都支持增量构建。要使自定义 task 与增量构建兼容,请指定输入和输出

build.gradle.kts
tasks.register("processTemplatesAdHoc") {
    inputs.property("engine", TemplateEngineType.FREEMARKER)
    inputs.files(fileTree("src/templates"))
        .withPropertyName("sourceFiles")
        .withPathSensitivity(PathSensitivity.RELATIVE)
    inputs.property("templateData.name", "docs")
    inputs.property("templateData.variables", mapOf("year" to "2013"))
    outputs.dir(layout.buildDirectory.dir("genOutput2"))
        .withPropertyName("outputDir")

    doLast {
        // Process the templates here
    }
}
build.gradle
tasks.register('processTemplatesAdHoc') {
    inputs.property('engine', TemplateEngineType.FREEMARKER)
    inputs.files(fileTree('src/templates'))
        .withPropertyName('sourceFiles')
        .withPathSensitivity(PathSensitivity.RELATIVE)
    inputs.property('templateData.name', 'docs')
    inputs.property('templateData.variables', [year: '2013'])
    outputs.dir(layout.buildDirectory.dir('genOutput2'))
        .withPropertyName('outputDir')

    doLast {
        // Process the templates here
    }
}

有关增量构建的更多信息,请查看增量构建文档

使用构建扫描时间线可视化增量构建

查看构建扫描时间线视图,以识别可能从增量构建中受益的 task。这也可以帮助你理解为什么 task 在你期望 Gradle 跳过它们时执行。

timeline
图 3. 时间线视图可以帮助进行增量构建检查

正如你在上面的构建扫描中看到的,该 task 不是最新的,因为它的一个输入("timestamp")已更改,迫使该 task 重新运行。

按持续时间对 task 进行排序,以查找项目中速度最慢的 task。

启用构建缓存

构建缓存是一种 Gradle 优化,它存储特定输入的 task 输出。当你稍后使用相同的输入运行相同的 task 时,Gradle 会从构建缓存中检索输出,而不是再次运行 task。默认情况下,Gradle 不使用构建缓存。要在构建时启用构建缓存,请使用 build-cache 标志

$ gradle <task> --build-cache

要默认启用构建缓存,请将以下设置添加到项目根目录或 Gradle home 目录下的 gradle.properties 文件中

gradle.properties
org.gradle.caching=true

你可以使用本地构建缓存来加快单台机器上的重复构建。你还可以使用共享构建缓存来加快多台机器上的重复构建。Develocity 提供了一个。共享构建缓存可以减少 CI 和开发者构建的构建时间。

有关构建缓存的更多信息,请查看构建缓存文档

使用构建扫描可视化构建缓存

构建扫描可以帮助你调查构建缓存的有效性。在性能屏幕中,“构建缓存”选项卡显示有关以下内容的统计信息:

  • 有多少 task 与缓存交互

  • 使用了哪个缓存

  • 这些缓存条目的传输和打包/解包速率

cache performance
图 4. 检查构建的构建缓存性能

“Task 执行”选项卡显示有关 task 可缓存性的详细信息。单击类别以查看时间线屏幕,该屏幕突出显示该类别的 task。

task execution cacheable
图 5. 面向 task 的性能视图
timeline not cacheable
图 6. 时间线屏幕,仅显示“不可缓存”的 task

在时间线屏幕上按 task 持续时间排序,以突出显示具有巨大节省时间潜力的 task。上面的构建扫描显示 :task1:task3 可以改进并使其可缓存,并显示了 Gradle 没有缓存它们的原因。

为特定的开发者工作流创建构建

最快的 task 是不执行的 task。如果你能找到跳过不需要运行的 task 的方法,你最终将获得更快的整体构建速度。

如果你的构建包含多个子项目,请创建 task 以独立构建这些子项目。这有助于你充分利用缓存,因为对一个子项目的更改不会强制重建不相关的子项目。这也有助于减少在不相关的子项目上工作的团队的构建时间:前端开发人员无需在每次更改前端时都构建后端子项目。文档编写者甚至不需要构建前端或后端代码,即使文档与代码位于同一项目中。

相反,创建符合开发者需求的 task。你仍然会拥有整个项目的单个 task 图。每个用户组都建议 task 图的受限视图:将该视图转换为排除不必要 task 的 Gradle 工作流。

Gradle 提供了几个功能来创建这些工作流:

  • 将 task 分配给适当的

  • 创建聚合 task:没有操作但仅依赖于其他 task 的 task,例如 assemble

  • 通过 gradle.taskGraph.whenReady() 等延迟配置,因此你可以在必要时才执行验证

增加堆大小

默认情况下,Gradle 为你的构建保留 512MB 的堆空间。这对于大多数项目来说已经足够了。但是,一些非常大的构建可能需要更多内存来容纳 Gradle 的模型和缓存。如果你的情况是这样,你可以指定更大的内存需求。在项目根目录或 Gradle home 目录下的 gradle.properties 文件中指定以下属性

gradle.properties
org.gradle.jvmargs=-Xmx2048M

要了解更多信息,请查看JVM 内存配置文档

优化配置

正如构建生命周期章节中所述,Gradle 构建经历 3 个阶段:初始化、配置和执行。配置代码始终执行,无论运行什么 task。因此,在配置期间执行的任何昂贵的工作都会减慢每次调用。即使是像 gradle helpgradle tasks 这样的简单命令。

接下来的几个小节介绍了可以减少配置阶段花费时间的技术。

你还可以启用配置缓存以减少慢速配置阶段的影响。但即使是使用缓存的机器,有时仍然会执行你的配置阶段。因此,你应该使用这些技术使配置阶段尽可能快。

避免昂贵或阻塞的工作

你应该避免在配置阶段进行耗时的工作。但有时它会偷偷潜入你的构建中,出现在不明显的地方。如果该代码在构建文件中,那么在配置期间加密数据或调用远程服务通常是很明显的。但像这样的逻辑更常见于插件,偶尔也会出现在自定义 task 类中。插件的 apply() 方法或 task 的构造函数中的任何昂贵的工作都是一个危险信号。

仅在需要的地方应用插件

你应用于项目的每个插件和脚本都会增加整体配置时间。有些插件的影响比其他插件更大。这并不意味着你应该避免使用插件,但你应该注意只在需要它们的地方应用它们。例如,即使并非每个项目都需要插件,也很容易通过 allprojects {}subprojects {} 将插件应用于所有子项目。

在上面的构建扫描示例中,你可以看到根构建脚本将 script-a.gradle 脚本应用于构建中的 3 个子项目

script a application
图 7. 显示 script-a.gradle 应用于构建

此脚本需要 1 秒才能运行。由于它应用于 3 个子项目,因此该脚本累积延迟配置阶段 3 秒。在这种情况下,有几种方法可以减少延迟

  • 如果只有一个子项目使用该脚本,你可以从其他子项目中删除脚本应用。这将在每次 Gradle 调用中减少两秒的配置延迟。

  • 如果多个子项目(但不是全部)使用该脚本,你可以将该脚本和所有周围逻辑重构为位于buildSrc中的自定义插件。将自定义插件仅应用于相关的子项目,从而减少配置延迟并避免代码重复。

静态编译 task 和插件

插件和 task 作者通常编写 Groovy,因为它语法简洁、API 扩展到 JDK 以及使用闭包的函数式方法。但 Groovy 语法带来了动态解释的成本。因此,Groovy 中的方法调用比 Java 或 Kotlin 中的方法调用花费更多的时间并使用更多的 CPU。

你可以使用静态 Groovy 编译来降低此成本:当你不需要显式动态功能时,将 @CompileStatic 注解添加到你的 Groovy 类中。如果你在方法中需要动态 Groovy,请将 @CompileDynamic 注解添加到该方法。

或者,你可以使用静态编译语言(如 Java 或 Kotlin)编写插件和 task。

警告: Gradle 的 Groovy DSL 严重依赖 Groovy 的动态功能。要在插件中使用静态编译,请切换到类似 Java 的语法。

以下示例定义了一个复制文件而无需动态功能的 task

src/main/groovy/MyPlugin.groovy
project.tasks.register('copyFiles', Copy) { Task t ->
    t.into(project.layout.buildDirectory.dir('output'))
    t.from(project.configurations.getByName('compile'))
}

此示例使用所有 Gradle “域对象容器”上可用的 register()getByName() 方法。域对象容器包括 task、配置、依赖项、扩展等。某些集合(如 TaskContainer)具有专用类型,其中包含额外的 create 等方法,该方法接受 task 类型。

当你使用静态编译时,IDE 可以

  • 快速显示与无法识别的类型、属性和方法相关的错误

  • 自动完成方法名称

优化依赖解析

依赖解析简化了将第三方库和其他依赖项集成到你的项目中的过程。Gradle 联系远程服务器以发现和下载依赖项。你可以优化引用依赖项的方式,以减少这些远程服务器调用。

避免不必要和未使用的依赖项

管理第三方库及其传递依赖项会给项目维护和构建时间增加显著成本。

注意未使用的依赖项:当第三方库停止使用但未从依赖项列表中删除时。这在重构期间经常发生。你可以使用 Gradle Lint 插件来识别未使用的依赖项。

如果你仅在第三方库中使用少量方法或类,请考虑

  • 在你的项目中自己实现所需的代码

  • 如果是开源的,则从库中复制所需的代码(并注明出处!)

优化仓库顺序

当 Gradle 解析依赖项时,它会按照声明的顺序搜索每个仓库。为了减少搜索依赖项花费的时间,请首先声明托管最多依赖项的仓库。这最大限度地减少了解析所有依赖项所需的网络请求数。

最小化仓库数量

将声明的仓库数量限制为使你的构建能够工作的最小值。

如果你正在使用自定义仓库服务器,请创建一个虚拟仓库,将多个仓库聚合在一起。然后,仅将该仓库添加到你的构建文件中。

最小化动态版本和快照版本

动态版本(例如“2.+”)和更改的版本(快照)强制 Gradle 联系远程仓库以查找新版本。默认情况下,Gradle 仅每 24 小时检查一次。但你可以使用以下设置以编程方式更改此设置

  • cacheDynamicVersionsFor

  • cacheChangingModulesFor

如果构建文件或初始化脚本降低了这些值,Gradle 会更频繁地查询仓库。当你不需要每次构建时都使用依赖项的绝对最新版本时,请考虑删除这些设置的自定义值。

使用构建扫描查找动态版本和更改的版本

你可以通过构建扫描找到所有具有动态版本的依赖项

dependency dynamic versions
图 8. 查找具有动态版本的依赖项

你或许可以使用像“1.2”和“3.0.3.GA”这样的固定版本,这些版本允许 Gradle 缓存版本。如果你必须使用动态版本和更改的版本,请调整缓存设置以最好地满足你的需求。

避免在配置期间进行依赖解析

依赖解析是一个昂贵的过程,无论是在 I/O 方面还是在计算方面。Gradle 通过缓存减少了所需的网络流量。但仍然存在成本。Gradle 在每次构建时都运行配置阶段。如果你在配置阶段触发依赖解析,则每次构建都会支付该成本。

切换到声明式语法

如果你评估配置文件,你的项目将在配置期间支付依赖解析的成本。通常 task 会评估这些文件,因为在你准备在 task action 中对它们执行某些操作之前,你不需要这些文件。假设你正在进行一些调试,并希望显示构成配置的文件。为了实现这一点,你可能会注入一个 print 语句

build.gradle.kts
tasks.register<Copy>("copyFiles") {
    println(">> Compilation deps: ${configurations.compileClasspath.get().files.map { it.name }}")
    into(layout.buildDirectory.dir("output"))
    from(configurations.compileClasspath)
}
build.gradle
tasks.register('copyFiles', Copy) {
    println ">> Compilation deps: ${configurations.compileClasspath.files.name}"
    into(layout.buildDirectory.dir('output'))
    from(configurations.compileClasspath)
}

files 属性强制 Gradle 解析依赖项。在此示例中,这发生在配置阶段。由于配置阶段在每次构建时都运行,因此现在所有构建都支付依赖解析的性能成本。你可以使用 doFirst() action 来避免此成本

build.gradle.kts
tasks.register<Copy>("copyFiles") {
    into(layout.buildDirectory.dir("output"))
    // Store the configuration into a variable because referencing the project from the task action
    // is not compatible with the configuration cache.
    val compileClasspath: FileCollection = configurations.compileClasspath.get()
    from(compileClasspath)
    doFirst {
        println(">> Compilation deps: ${compileClasspath.files.map { it.name }}")
    }
}
build.gradle
tasks.register('copyFiles', Copy) {
    into(layout.buildDirectory.dir('output'))
    // Store the configuration into a variable because referencing the project from the task action
    // is not compatible with the configuration cache.
    FileCollection compileClasspath = configurations.compileClasspath
    from(compileClasspath)
    doFirst {
        println ">> Compilation deps: ${compileClasspath.files.name}"
    }
}

请注意,from() 声明不会解析依赖项,因为你使用的是依赖配置本身作为参数,而不是文件。Copy task 在 task 执行期间解析配置本身。

使用构建扫描可视化依赖解析

构建扫描的性能页面上的“依赖解析”选项卡显示配置和执行阶段的依赖解析时间

bad dependency resolution
图 9. 配置时的依赖解析

构建扫描提供了另一种识别此问题的方法。你的构建在“项目配置”期间应该花费 0 秒来解析依赖项。此示例显示构建在生命周期中过早地解析了依赖项。你还可以在“性能”页面上找到“设置和建议”选项卡。这显示了在配置阶段解析的依赖项。

删除或改进自定义依赖解析逻辑

Gradle 允许用户以最适合他们的方式建模依赖解析。简单的自定义,例如强制使用特定版本的依赖项或将一个依赖项替换为另一个依赖项,对依赖解析时间没有太大的影响。更复杂的自定义,例如下载和解析 POM 的自定义逻辑,可能会显著减慢依赖解析速度。

使用构建扫描或性能分析报告来检查自定义依赖解析逻辑是否会对依赖解析时间产生不利影响。这可能是你自行编写的自定义逻辑,也可能是插件的一部分。

删除缓慢或意外的依赖项下载

缓慢的依赖项下载会影响你的整体构建性能。有几个原因可能导致这种情况,包括互联网连接速度慢或仓库服务器过载。在构建扫描的“性能”页面上,你会找到“网络活动”选项卡。此选项卡列出了包括以下信息:

  • 下载依赖项花费的时间

  • 依赖项下载的传输速率

  • 按下载时间排序的下载列表

在以下示例中,两次缓慢的依赖项下载分别花费了 20 秒和 40 秒,并减慢了构建的整体性能

slow dependency downloads
图 10. 识别缓慢的依赖项下载

检查下载列表中是否有意外的依赖项下载。例如,你可能会看到由使用动态版本的依赖项引起的下载。

通过切换到其他仓库或依赖项来消除这些缓慢或意外的下载。

优化 Java 项目

以下各节仅适用于使用 java 插件或其他 JVM 语言的项目。

优化测试

项目通常花费大量构建时间进行测试。这些测试可能是单元测试和集成测试的混合。集成测试通常需要更长的时间。构建扫描可以帮助您识别最慢的测试。然后,您可以专注于加速这些测试。

tests longest
图 11. “测试”屏幕,按项目显示测试,并按持续时间排序

上面的构建扫描显示了所有运行测试的项目的交互式测试报告。

Gradle 提供了几种加速测试的方法

  • 并行执行测试

  • 将测试 Fork 到多个进程中

  • 禁用报告

让我们依次查看这些方法。

并行执行测试

Gradle 可以并行运行多个测试用例。要启用此功能,请覆盖相关 Test 任务上 maxParallelForks 的值。为了获得最佳性能,请使用小于或等于可用 CPU 核心数的数字

build.gradle.kts
tasks.withType<Test>().configureEach {
    maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
build.gradle
tasks.withType(Test).configureEach {
    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
}

并行测试必须是独立的。它们不应共享文件或数据库等资源。如果您的测试确实共享资源,它们可能会以随机和不可预测的方式相互干扰。

将测试 Fork 到多个进程中

默认情况下,Gradle 在单个 Fork VM 中运行所有测试。如果测试很多,或者某些测试消耗大量内存,则您的测试可能需要比您预期更长的时间才能运行。您可以增加堆大小,但是垃圾回收可能会减慢测试速度。

或者,您可以在一定数量的测试运行后,使用 forkEvery 设置 Fork 一个新的测试 VM

build.gradle.kts
tasks.withType<Test>().configureEach {
    forkEvery = 100
}
build.gradle
tasks.withType(Test).configureEach {
    forkEvery = 100
}
Fork VM 是一项昂贵的操作。在此处设置过小的值会减慢测试速度。

禁用报告

无论您是否要查看测试报告,Gradle 都会自动创建测试报告。报告生成会减慢整体构建速度。如果出现以下情况,您可能不需要报告

  • 您只关心测试是否成功(而不是原因)

  • 您使用构建扫描,它提供比本地报告更多的信息

要禁用测试报告,请在 Test 任务中将 reports.html.requiredreports.junitXml.required 设置为 false

build.gradle.kts
tasks.withType<Test>().configureEach {
    reports.html.required = false
    reports.junitXml.required = false
}
build.gradle
tasks.withType(Test).configureEach {
    reports.html.required = false
    reports.junitXml.required = false
}
有条件地启用报告

您可能希望有条件地启用报告,这样您就不必编辑构建文件即可查看它们。要在报告之前基于项目属性启用报告,请检查属性是否存在以禁用报告

build.gradle.kts
tasks.withType<Test>().configureEach {
    if (!project.hasProperty("createReports")) {
        reports.html.required = false
        reports.junitXml.required = false
    }
}
build.gradle
tasks.withType(Test).configureEach {
    if (!project.hasProperty("createReports")) {
        reports.html.required = false
        reports.junitXml.required = false
    }
}

然后,在命令行中使用 -PcreateReports 传递属性以生成报告。

$ gradle <task> -PcreateReports

或者在项目根目录或 Gradle 主目录中的 gradle.properties 文件中配置属性

gradle.properties
createReports=true

优化编译器

Java 编译器速度很快。但是,如果您要编译数百个 Java 类,即使是很短的编译时间也会累积起来。Gradle 为 Java 编译提供了几种优化

  • 将编译器作为单独的进程运行

  • 将仅限内部的依赖项切换为实现可见性

将编译器作为单独的进程运行

您可以为任何 JavaCompile 任务使用以下配置,将编译器作为单独的进程运行

build.gradle.kts
<task>.options.isFork = true
build.gradle
<task>.options.fork = true

要将配置应用于所有 Java 编译任务,您可以 configureEach java 编译任务

build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    options.isFork = true
}
build.gradle
tasks.withType(JavaCompile).configureEach {
    options.fork = true
}

Gradle 在构建期间重用此进程,因此 Fork 开销很小。通过将内存密集型编译 Fork 到单独的进程中,我们可以最大限度地减少主 Gradle 进程中的垃圾回收。更少的垃圾回收意味着 Gradle 的基础设施可以运行得更快,尤其是在您还使用 并行构建 时。

Fork 编译很少影响小型项目的性能。但是,如果单个任务一起编译超过一千个源文件,则应考虑使用它。

将仅限内部的依赖项切换为实现可见性

只有库可以定义 api 依赖项。使用 java-library 插件在您的库中定义 API 依赖项。使用 java 插件的项目无法声明 api 依赖项。

在 Gradle 3.4 之前,项目使用 compile 配置声明依赖项。这会将所有这些依赖项暴露给下游项目。在 Gradle 3.4 及更高版本中,您可以将面向下游的 api 依赖项与仅限内部的 implementation 详细信息分开。实现依赖项不会泄漏到下游项目的编译类路径中。当实现细节发生更改时,Gradle 仅重新编译 api 依赖项。

build.gradle.kts
dependencies {
   api(project("my-utils"))
   implementation("com.google.guava:guava:21.0")
}
build.gradle
dependencies {
   api project('my-utils')
   implementation 'com.google.guava:guava:21.0'
}

这可以显着减少大型多项目构建中由单个更改引起的重新编译的“涟漪”。

提高旧版本 Gradle 的性能

有些项目无法轻松升级到当前的 Gradle 版本。虽然您应始终在可能的情况下将 Gradle 升级到最新版本,但我们认识到,对于某些特定的情况,这并非总是可行的。在这些精选案例中,请查看以下建议以优化旧版本的 Gradle。

启用 Daemon

Gradle 3.0 及更高版本默认启用 Daemon。如果您使用的是旧版本,则应更新到最新版本的 Gradle。如果您无法更新 Gradle 版本,则可以手动启用 Daemon

使用增量编译

Gradle 可以分析依赖项,细化到单个类级别,以仅重新编译受更改影响的类。Gradle 4.10 及更高版本默认启用增量编译。要在旧版本的 Gradle 中默认启用增量编译,请将以下设置添加到您的 build.gradle 文件中

build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    options.isIncremental = true
}
build.gradle
tasks.withType(JavaCompile).configureEach {
    options.incremental = true
}

使用编译避免

通常,更新仅更改代码的内部实现细节,例如方法的主体。这些更新称为ABI 兼容更改:它们对项目的二进制接口没有影响。在 Gradle 3.4 及更高版本中,ABI 兼容的更改不再触发下游项目的重新编译。这尤其提高了具有深度依赖链的大型多项目构建的构建时间。

升级到 Gradle 3.4 以上的版本以受益于编译避免。

如果您使用注解处理器,则需要显式声明它们,以便编译避免能够工作。要了解更多信息,请查看编译避免文档

优化 Android 项目

此页面上的所有内容都适用于 Android 构建,因为 Android 构建使用 Gradle。然而,Android 引入了独特的优化机会。有关更多信息,请查看Android 团队性能指南。您还可以观看 Google IO 2017 的配套演讲