一、前言
在Gradle中,学习和使用Task是我们必不可少的内容。什么是Task?Task就是一个操作,你可以用来移动复制文件、自定义输出打包apk文件名或者上传一些文件或者jar到Maven仓库等。再简单地说,可以理解成一个Java方法。Android中最常见的Task,如下:
task clean(type: Delete) {
delete rootProject.buildDir
}
可以看到,他的任务就是删除根目录下的buildDir文件夹 。接下来,让我们看看如何创建一个任务😀
二、创建一个任务
PS:以下的代码都是放在build.gradle中运行,如果想在单独的gradle文件运行,必须apply进项目里才行,例如 apply from: 'custom.gradle'。
在Gradle中,创建一个任务的方式很简单,如下:
task printfHello{
println "printfHello 配置阶段"
doFirst {
println "Hello doFirst"
}
doLast {
println "Hello doLast"
}
}
如上所示,一个简单的任务就创建成功了,其中doFirst,doLast分别代码在任务的执行前后对任务的操作。
上面的代码等同于如下代码:
task("printfHello"){
println "printfHello 配置阶段"
doFirst {
println "Hello doFirst"
}
doLast {
println "Hello doLast"
}
}
//或者
def Task customTask = task("customTask")
customTask.doFirst {
println "customTask start"
}
如何运行我们的任务呢?
1.点击代码旁边的绿色标志
2.在Terminal下输入如下指令:
gradlew printfHello
效果如下:
可以看到,分别输出了配置阶段的任务代码和运行阶段的任务代码。从此也能看出,一开始clean Task就在配置阶段进行文件夹的删除。
关于Gradle的生命周期可以看这里,其实也就三个阶段,初始化阶段 》》配置阶段 》》运行阶段
接下来我们可以看下task对应的源码,如下:
Task task(String name, Closure configureClosure);
其实task就是Project对象的一个方法,第一个参数是任务名,第二个参数是一个闭包,这个闭包的作用就是对我们创建的任务进行相关的配置和执行。
除了上面的方法,还可以通过TaskContaniner集合来创建。代码如下:
tasks.create("printHelloWorld"){
doFirst {
println "Hello World doFirst"
}
doLast {
println "Hello World doLast"
}
}
这两种创建任务的方式作用都是一样的,只是创建的方式不一样。一般使用第一种,会比较简洁。
三、任务依赖
每个任务之间是有依赖的,可以方便我们控制任务执行的顺序。例如一个Apk的生成,肯定需要先把Java转成Class,再把Class转成Dex文件,然后再把Dex文件与其他资源文件合成一个Apk文件。下面让我们看看任务间是如何依赖的,如下:
//准备内存
task buildMemory{
doFirst {
println "buildMemory start"
}
doLast {
println "buildMemory finish"
}
}
//准备CPU
task buildCpu{
doFirst {
println "buildCpu start"
}
doLast {
println "buildCpu finish"
}
}
//组装电脑
task assembleComputer{
dependsOn(buildCpu,buildMemory)
doFirst {
println "assembleComputer start"
}
doLast {
println "assembleComputer finish"
}
}
如上面的代码,依赖另外一个task的关键方法是dependsOn,参数是依赖的task任务,也就是先执行的任务。运行效果如下:
> Task :buildCpu
buildCpu start
buildCpu finish
> Task :buildMemory
buildMemory start
buildMemory finish
> Task :assembleComputer
assembleComputer start
assembleComputer finish
依赖任务的方式还有如下写法:
task assembleComputer(dependsOn: [buildCpu,buildMemory])
四、自定义继承Task
不知道你会不会有疑惑,task有doFirst,doLast方法,那任务执行中的方法在哪里?如果想知道答案的话,继续往下看。先上一段代码,如下:
def customTask = task customtask(type:CustomTask)
customTask.doFirst {
println "CustomTask doFirst"
}
customTask.doLast {
println "CustomTask doLast"
}
abstract class CustomTask extends DefaultTask {
@Override
Task doFirst(Action<? super Task> action) {
return super.doFirst(action)
}
@TaskAction
def doSelf(){
println "CustomTask doSelf"
}
}
先解释一下几个关键:
- type是一个关键字,用来引入一个存在的Task,例如我们创建的CustomTask
- DefaultTask,用来提供给用户自定义Task的基类
- @TaskAction是一个注解标注,意思是被标注的方法就是Task要执行的方法,可以多个。
执行代码效果图如下:
> Task :customtask
CustomTask doFirst
CustomTask doSelf
CustomTask doLast
可以看到,任务的执行前、执行中、执行后的逻辑都出来了。
那他是如何做到这样顺序执行的呢?
原理很简单,DefaultTask维护了一个actions的List,那只要我们按顺序添加doFirst、doSelf(把自己执行的代码叫doSelf)、doLast,然后便利执行actions List,那不久可以做到如上面的效果。
doFirst添加进actions List(往0下标下标插入)源码如下:
public Task doFirst(final Closure action) {
taskMutator.mutate("Task.doFirst(Closure)", new Runnable() {
@Override
public void run() {
getTaskActions().add(0, convertClosureToAction(action, "doFirst {} action"));
}
});
}
doLast添加进actions List(往最后插入)源码如下:
public Task doLast(final Closure action) {
taskMutator.mutate("Task.doLast(Closure)", new Runnable() {
@Override
public void run() {
getTaskActions().add(convertClosureToAction(action, "doLast {} action"));
}
});
return this;
}
可以看到,doFirst下标的位置肯定是在doLast之前的,再来看看doSelf是如何执行的。
doSelf添加进actions List(往0下标下标插入)源码如下:
public void prependParallelSafeAction(final Action<? super Task> action) {
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
getTaskActions().add(0, wrap(action));
}
看到这里,你会不会疑惑?也是往0下标插入,那如何保证顺序呢?
答案是肯定可以保证的,因为这个方法的调用是在解析@TaskAction标注的时候执行的,也就是此方法被执行时actions List里面的元素是空的。
五、其他功能补充
5.1 强制排序
在你同时执行多个任务的时候,可能需要任务B必须在任务A执行完才能执行。代码如下:
taskB.mustRunAfter(taskA) //表示taskB必须在taskA后执行
taskB.shouldRunAfter(taskA) //表示taskB应该在taskA后执行,这个相比较mustRunAfter,可能会出现意外的情况
示例如下:
task getBook{
doLast {
println "getBook finish"
}
}
task learnEnglish{
doFirst {
println "learnEnglish start"
}
doLast {
println "learnEnglish finish"
}
}
learnEnglish.mustRunAfter(getBook) //表示learnEnglish任务必须在getBook任务后执行
执行如下指令:
gradlew learnEnglish getBook
如果没有加mustRunAfter,那么就会按照代码顺序执行,先执行learnEnglish再getBook。加了mustRunAfter之后,执行效果如下:
5.2 启用/禁止任务
Task中有一个enable属性,表示启用或禁止任务,默认是true,表示启用;false表示禁止任务执行,执行时会被跳过。修改上面的getBook代码,增加enable为禁止,如下:
task getBook{
enabled(false)
}
执行代码,getBook任务没有被执行到,效果图如下:
5.3 任务的onlyIf
Task中有一个onlyIf的方法,如果返回true,说明任务可以执行,否则就跳过。我们继续给getBook增加一个onlyIf方法,用来判断获取的书籍是不是英语书,如下代码:
getBook.onlyIf{
def book = "世界百科"
if (book =="英语书"){
return true
}else {
false
}
}
执行代码,getBook任务没有被执行到,效果图如下:
PS:这个onlyIf方法可以使用在任何有Task的场景,不要局限在mustRunAfter,mustRunAfter只是控制Task的执行顺序。
5.4 任务的分组和描述
在Task中,我们可以给Task分组和添加描述,可以让开发者更加直观的知道这个Task的作用。代码如下:
task learnEnglish{
group(BasePlugin.BUILD_GROUP)
description("这个一个learnEnglish Task")
}
- group:给task分组,可以显示在对应的分组里,例如上面BUILD_GROUP,显示在build分组,其他的选项也是如此效果。
- description:给task添加一个描述,可以通过gradlew tasks命令查看
Sync Now代码,在build中多了一个learnEnglish Task,