前面通过很多范例讲了在 build.gradle 中怎么创建 Task,但是 Task 到底是个什么东西,它里面有些什么,我们并不清楚,本文试图揭开 Task 神秘的面纱。
1. Task类图
Gradle 所说的 Task 是 org.gradle.api.Task 接口,默认实现是 org.gradle.api.DefaultTask 类,其类图大概如下(只节选了比较重要的部分):
我们只需关注 Task 接口即可,从这里基本可以了解到 Task 有哪些特性。
2. Task接口解析
为了了解每个接口方法的含义,我们直接用代码来测试验证下:
class SayHelloTask extends DefaultTask {
String msg = "default name";
int age = 20
@TaskAction
void sayHello() {
println "Hello $msg ! Age is ${age}"
}
}
task test1 << {
println "task test1 exec..."
}
task test2 << {
println "task test2 exec..."
}
task test3 << {
println "task test3 exec..."
}
task hello(type: SayHelloTask, group: "MyGroup")
//对task进行配置,
hello.configure {
println "hello task configure"
msg = "hjy"
}
//获取task的名称
println "task name is ${hello.getName()}"
//获取task的组名
println "task group is ${hello.getGroup()}"
//设置task里的属性值,设置 age = 70
hello.setProperty("age", 70)
//获取task里的某个属性值
println "task msg is ${hello.property('msg')}"
//设置依赖的task,只有test1 task执行完后才会执行hello task
hello.dependsOn(test1)
//设置终结者任务,执行完hello task之后会执行test2 task,通常可以用该方法做一些清理操作
hello.finalizedBy(test2)
//如果同时执行hello、test3这2个task,会确保test3执行完之后才执行hello这个task,用这个来保证执行顺序
hello.setMustRunAfter([test3])
//设置满足某个条件后才执行该task
hello.setOnlyIf {
//只有当 age = 70 时,才会执行task,否则不会执行
return hello.property("age") == 70
}
我们执行任务gradle hello test3,结果如下:
> Configure project :
hello task configure
task name is hello
task group is MyGroup
task msg is hjy
> Task :test3
task test3 exec...
> Task :test1
task test1 exec...
> Task :hello
Hello hjy ! Age is 70
> Task :test2
task test2 exec...
3. TaskContainer接口解析
TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。
org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection
//创建task
create(name: String): Task
create(name: String, configure: Closure): Task
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task
//当task被加入到TaskContainer时的监听
whenTaskAdded(action: Closure)
我们先来看看创建 task 的方法:
//当有task创建时
getTasks().whenTaskAdded { Task task ->
println "The task ${task.getName()} is added to the TaskContainer"
}
//采用create(name: String)创建
getTasks().create("task1")
//采用create(options: Map<String, ?>)创建
getTasks().create([name: "task2", group: "MyGroup", description: "这是task2描述", dependsOn: ["task1"]])
//采用create(options: Map<String, ?>, configure: Closure)创建
getTasks().create("task3", {
group "MyGroup"
setDependsOn(["task1", "task2"])
setDescription "这是task3描述"
})
执行命令gradle -q tasks --all,查看是否创建成功,结果如下:
MyGroup tasks
-------------
task2 - 这是task2描述
task3 - 这是task3描述
Other tasks
-----------
task1
我们再来试试查找 task 的方法:
//通过名字查找指定的task
def task3 = getTasks().findByName("task3")
println "findByName() return task is " + task3
def taskList = getTasks().withType(DefaultTask)
def count = 0
//遍历所有的task,打印出其名字
taskList.all { Task t ->
println "${count++} task name is ${t.name}"
}
4. Task增量构建
Gradle 支持一种叫做 up-to-date 检查的功能,也就是常说的增量构建。Gradle 的 Task 会把每次运行的结果缓存下来,当下次运行时,会检查输出结果有没有变更,如果没有变更则跳过运行,这样可以提高 Gradle 的构建速度。
通常,一个 task 会有一些输入(inputs)和一些输出(outputs),task 的输入会影响其输出结果,以官网中的一张图为例:
图中表示一个 java 编译的 task,它的输入有2种,一是 JDK 版本号,一是源文件,它的输出结果为 class 文件,只要 JDK 版本号与源文件有任何变动,最终编译出的 class 文件肯定是不同的。当我们执行过一次编译任务后,再次运行该 task ,如果发现它的输入没有任何改变,那么它编译后的结果肯定也是不会变化的,可以直接从缓存里获取输出,这样 Gradle 会标识该 task 为 UP-TO-DATE,进而跳过该 task 的执行。
4.1 TaskInputs、TaskOutputs
那么怎么实现一个增量构建呢?一个增量构建必须至少指定一个输入、一个输出,从前面 Task 的类图中可以看到,Task.getInputs() 对象类型为 TaskInputs,Task.getOutputs() 对象类型为 TaskOuputs,从中也可以看到inputs、outputs都支持哪些数据类型。同样以一个简单例子来说明:
task test1 {
//设置inputs
inputs.property("name", "hjy")
inputs.property("age", 20)
//设置outputs
outputs.file("$buildDir/test.txt")
doLast {
println "exec task task1"
}
}
task test2 {
doLast {
println "exec task task2"
}
}
连续2次运行task,执行命令gradle test1 test2,结果如下:
//第一次的运行结果
> Task :test1
exec task task1
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
//第二次的运行结果
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date
从结果中可以看到,第2次运行时,test1 task 并没有运行,而是被标记为 up-to-date,而 test2 task 则每次都会运行,这就是典型的增量构建。
4.2 taskInputs、taskOutputs注解
当你自定义 task class 时,可以通过注解来实现增量构建,这是一种更加灵活方便的方式。我们常用的注解包括:
注解名 | 属性类型 | 描述 |
---|---|---|
@Input | 任意Serializable类型 | 一个简单的输入值 |
@InputFile | File | 一个输入文件,不是目录 |
@InputDirectory | File | 一个输入目录,不是文件 |
@InputFiles | Iterable<File> | File列表,包含文件和目录 |
@OutputFile | File | 一个输出文件,不是目录 |
@OutputDirectory | File | 一个输出目录,不是文件 |
@OutputFiles | Map<String, File>或Iterable<File> | 输出文件列表 |
@OutputDirectories | Map<String, File>或Iterable<File> | 输出目录列表 |
我们通过自定义一个 task class 来演示一下用法:
class SayHelloTask extends DefaultTask {
//定义输入
@Input
String username;
@Input
int age
//定义输出
@OutputDirectory
File destDir;
@TaskAction
void sayHello() {
println "Hello $username ! age is $age"
}
}
task test(type: SayHelloTask) {
age = 18
username = "hjy"
destDir = file("$buildDir/test")
}
执行该 task 之后,会自动生成一个 $buildDir/test 文件目录,当你再次运行时就实施增量构建。但是当你修改 age、username 的值,或者删除磁盘上 $buildDir/test 目录,再次运行该 task ,该 task 就会重新运行。
系列文章
Android Gradle学习(一):Gradle基础入门
Android Gradle学习(二):如何创建Task
Android Gradle学习(三):Task进阶学习
Android Gradle学习(四):Project详解
Android Gradle学习(五):Extension详解
Android Gradle学习(六):NamedDomainObjectContainer详解
Android Gradle学习(七):Gradle构建生命周期
Android Gradle学习(八):统计Task执行时长
Android Gradle学习(九):一些有用的小技巧