在上篇文章中,我们聊到了如何读懂 Gradle 的语法。今天接着讲讲增量式构建,这可是提升项目编译效率的关键机制。
如果你把 Gradle 的 Task 看作一个黑盒子,核心就在于输入和输出。Task 对输入进行操作,产生输出。比如用 Java 插件编译源码时,输入是 Java 源文件,输出就是 class 文件。如果多次执行同一个 Task,输入输出都没变,那重复执行就是纯粹的浪费时间。
为了解决这个问题,Gradle 引入了增量式构建。每个 Task 都可以定义 inputs(输入)和 outputs(输出)。执行时,Gradle 会对比当前状态与上一次缓存的状态。如果没变化,Task 就会被标记为 UP-TO-DATE(最新),直接跳过执行。inputs 和 outputs 可以是文件、文件夹、Project Property,甚至是一个闭包条件。
每个 Task 都有 inputs 和 outputs 属性,类型分别是 TaskInputs 和 TaskOutputs。来看个具体场景:有个名为 combineFileContent 的 Task,它读取 sourceDir 目录下的所有文件,合并内容到 destination.txt。先看看没定义输入输出的情况:
task combineFileContentNonIncremental {
def sources = fileTree('sourceDir')
def destination = file('destination.txt')
doLast {
destination.withPrintWriter { writer ->
sources.each { source ->
writer.println source.text
}
}
}
}
多次执行 gradle combineFileContentNonIncremental 时,无论文件是否变动,整个 Task 都会跑一遍。要是这个 Task 很耗时,反复执行就太亏了。
这时候,只要声明 sources 为 inputs,destination 为 outputs,稍微改一下代码就行:
task combineFileContentIncremental {
def sources = fileTree('sourceDir')
def destination = file('destination.txt')
inputs.dir sources
outputs.file destination
doLast {
destination.withPrintWriter { writer ->
sources.each { source ->
writer.println source.text
}
}
}
}
相比上一个版本,只多了两行配置:inputs.dir sources 和 outputs.file destination。
首次执行时,Gradle 还是会完整运行。但紧接着再跑一次,命令行就会显示:
:combineFileContentIncremental UP-TO-DATE BUILD SUCCESSFUL Total time: 2.104 secs
看到 UP-TO-DATE 就说明任务被跳过了。实际开发中,很多 Gradle 插件都内置了这种机制。
当然,如果你修改了 inputs(比如 sourceDir 里的文件)或者删除了 outputs(destination.txt),再次调用命令时,Gradle 会发现状态变了,于是重新执行。对于 outputs,还可以用 upToDateWhen() 方法配合闭包来灵活判断任务是否需要更新。
关于如何自定义 Project 的 Property,我们在后续内容里会详细展开。

