构建性能对生产力至关重要。构建完成所需的时间越长,它们越有可能破坏你的开发流程。构建每天运行很多次,因此即使是短暂的等待时间也会累积起来。持续集成 (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 每次只运行一个任务。要并行执行属于不同子项目的任务,请使用 `parallel` 标志。

$ gradle <task> --parallel

要默认情况下并行执行项目任务,请将以下设置添加到项目根目录或 Gradle 主目录中的 `gradle.properties` 文件中

gradle.properties
org.gradle.parallel=true

并行构建可以显着缩短构建时间;缩短多少取决于您的项目结构以及子项目之间有多少依赖项。构建执行时间主要由单个子项目决定的构建将不会获得太多好处。具有大量子项目间依赖关系的项目也是如此。但是大多数多子项目构建都会缩短构建时间。

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

构建扫描为您提供了任务执行的视觉时间线。在以下示例构建中,您可以看到构建开始和结束时运行时间较长的任务

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

调整构建配置以尽早并行运行两个缓慢的任务,将整体构建时间从 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 主目录中的 `gradle.properties` 文件中

gradle.properties
org.gradle.daemon=true

在开发人员机器上,您应该会看到性能显着提升。在 CI 机器上,长期运行的代理会从 Daemon 中受益。但是,短期运行的机器不会获得太多好处。在 Gradle 3.0 及更高版本中,Daemon 会在内存压力下自动关闭,因此始终可以安全地启用 Daemon。

启用配置缓存

此功能存在以下限制

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

  • 您的构建和您依赖的插件可能需要进行更改以满足 要求

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

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

构建配置输入包括

  • 初始化脚本

  • 设置脚本

  • 构建脚本

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

  • 配置阶段使用的 Gradle 属性

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

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

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

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

$ gradle <task> --configuration-cache

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

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

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

配置缓存的其他优势

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

  • 并行执行所有任务,即使是在同一个子项目中的任务。

  • 缓存依赖项解析结果。

为自定义任务启用增量构建

增量构建是 Gradle 的一项优化,它会跳过之前已使用相同输入执行的任务。如果任务的输入及其输出自上次执行以来没有发生变化,Gradle 会跳过该任务。

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

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
    }
}

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

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

查看构建扫描时间线视图,以识别可以从增量构建中受益的任务。这也可以帮助您了解为什么在您期望 Gradle 跳过任务时,任务会执行。

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

如您在上面的构建扫描中看到的那样,该任务不是最新的,因为它的一个输入(“时间戳”)发生了变化,迫使该任务重新运行。

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

启用构建缓存

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

$ gradle <task> --build-cache

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

gradle.properties
org.gradle.caching=true

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

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

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

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

  • 有多少任务与缓存交互

  • 使用了哪个缓存

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

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

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

task execution cacheable
图 5. 任务导向的性能视图
timeline not cacheable
图 6. 仅包含“不可缓存”任务的时间线屏幕

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

为特定开发人员工作流程创建构建

最快的任务是不执行的任务。如果您能找到跳过不需要运行的任务的方法,最终将获得更快的构建。

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

相反,创建与开发人员需求相匹配的任务。您仍然将拥有整个项目的单个任务图。每组用户都建议对任务图进行限制性查看:将该视图转换为排除不必要任务的 Gradle 工作流程。

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

  • 将任务分配给适当的

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

  • 通过gradle.taskGraph.whenReady()等推迟配置,以便您仅在必要时执行验证

增加堆大小

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

gradle.properties
org.gradle.jvmargs=-Xmx2048M

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

优化配置

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

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

您还可以 启用配置缓存 来减少缓慢的配置阶段的影响。但是,即使使用缓存的机器也会偶尔执行您的配置阶段。因此,您应该使用这些技术使配置阶段尽可能快。

避免昂贵或阻塞工作

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

只在需要的地方应用插件

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

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

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

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

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

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

静态编译任务和插件

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

您可以通过静态 Groovy 编译来降低这种成本:当您明确不需要动态特性时,在您的 Groovy 类中添加 @CompileStatic 注解。如果您需要在方法中使用动态 Groovy,请在该方法中添加 @CompileDynamic 注解。

或者,您可以使用 Java 或 Kotlin 等静态编译语言编写插件和任务。

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

以下示例定义了一个没有动态特性的复制文件的任务

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() 方法。域对象容器包括任务、配置、依赖项、扩展等。一些集合,例如 TaskContainer,具有专用类型,这些类型具有额外的方法,例如 create,它接受一个任务类型。

当您使用静态编译时,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 在每次构建时都会运行配置阶段。如果您在配置阶段触发依赖项解析,每次构建都会支付该成本。

切换到声明式语法

如果您评估配置文件,您的项目会在配置期间支付依赖项解析的成本。通常,任务会评估这些文件,因为您在准备好使用任务操作中的这些文件之前不需要它们。假设您正在进行一些调试,并且想要显示构成配置的文件。为了实现这一点,您可能会注入一个打印语句

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() 操作避免此成本

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任务在任务执行期间解析配置本身。

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

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

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

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

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

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

使用构建扫描或配置文件报告检查自定义依赖项解析逻辑是否会对依赖项解析时间产生负面影响。这可能是您自己编写的自定义逻辑,也可能是插件的一部分。

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

缓慢的依赖项下载会影响您的整体构建性能。这可能由多种因素引起,包括缓慢的互联网连接或负载过重的存储库服务器。在构建扫描的“性能”页面上,您会找到一个“网络活动”选项卡。此选项卡列出了包括以下信息:

  • 下载依赖项所花费的时间

  • 依赖项下载的传输速率

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

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

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

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

通过切换到不同的存储库或依赖项来消除这些缓慢或意外的下载。

优化 Java 项目

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

优化测试

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

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

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

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

  • 并行执行测试

  • 将测试分叉到多个进程

  • 禁用报告

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

并行执行测试

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
}

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

将测试分叉到多个进程

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

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

build.gradle.kts
tasks.withType<Test>().configureEach {
    forkEvery = 100
}
build.gradle
tasks.withType(Test).configureEach {
    forkEvery = 100
}
分叉 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 在构建持续时间内重用此进程,因此分叉开销很小。通过将内存密集型编译分叉到单独的进程中,我们最大限度地减少了主 Gradle 进程中的垃圾回收。更少的垃圾回收意味着 Gradle 的基础设施可以更快地运行,尤其是在您也使用 并行构建 时。

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

将内部依赖项切换到实现可见性

只有库可以定义 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。

启用守护进程

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

使用增量编译

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 兼容更改不再触发下游项目的重新编译。这尤其提高了具有深层依赖链的大型多项目构建的构建时间。

升级到 3.4 以上的 Gradle 版本以从编译避免中获益。

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

优化 Android 项目

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