task在gradle占有很重要的地位,因为在gradle中任何执行操作都是通过task来执行。task可以理解成任务,作用就是执行某些指定的操作。
以Android为例,Gradle构建编译一个Android项目的时候,需要执行很多操作流程。整个过程可以通过命令行gradle assembleRelease --info
来看一下:
我们会看到很多task,这些task就是一个一个任务,里面执行特定的操作流程,比如preBuild负责准备配置文件,preReleaseUnitTestBuild负责准备release下的单元测试构建,generateReleaseResources准备资源文件等等,一系列的task执行起来,构建一个完整的apk文件。
一. project
在深入理解task之前,还需要理解另外一个重要而又跟task很大关联的东西:project。
每个可以构建编译的模块就是一个project。还是以Android为例,无论是library,application都是一个project,更简单的理解就是看这个模块是否有build.gradle文件,如果有那么就是一个project。因为project有构建编译等其他的需求,那么自然就需要task,所以project里面包含至少一个task。
为什么说有的build.gradle的模块就是一个project?因为Gradle会默认为每个build.gradle根据配置分配一个project对象,所有我们也能在build.gradle里面默认使用project对象及相对应的API。在build.gradle中,你可以通过API来访问gradle的所有特性,比如task的创建以及依赖管理,在实际开发中,你也会接触到很多API。
下面列出一个project的常用的API图:
这里面的API我们都可以直接在build.gradle里面直接使用,而且不需要实例化project对象,因为每个build.gradle都是分配一个project对象。
通过以上,大概也知道project以及project和task之间的关系了。如果还是不明白,打个比喻:project就是一个公司部门,task就是部分的员工,project的运作只有靠一个个task各司其职才能运作起来。
二. task
2.1 action
上面我们提到task的作用就是执行操作,那么执行操作的动作也就是放置构建逻辑的地方应该如何添加呢?task有个概念-action,就是要执行的动作,内部通过一个集合管理action,task执行的时候像消息队列一样,一个一个action消费执行掉。task提供了doFirst和doLast两个函数来添加action:doFirst ,doLast ,用于添加需要最先执行的Action和需要和需要最后执行的Action。 看个例子:
task helllTask {
doFirst {
println "Task doFirst"
}
doLast {
println "Task doLast"
}
}
我们给helllTask上添加两个action,分别是
{
println "Task doFirst"
}
{
println "Task doLast"
}
还是套用之前的部分跟员工的例子,task是一个一个员工的话,那么action就是分配的任务,分别可以选择上班的时候(doFirst)做跟下班的时候做(doLast)。
2.2 依赖
task 有时候需要执行很多很多操作,有些操作跟其他的task是一样的,这时候就可以将这些相同操作的task抽取出来一个基础的task,然后其他的task分别依赖这个基础的task。
task One {
doFirst {
println "一言不合先来个1"
}
}
task Two {
doFirst {
println "一言不合先来个2"
}
}
task Three(dependsOn: [One, Two]) {
doFirst {
println "我需要1跟2"
}
}
终端执行:gradle Three
得到:
> Task :One
一言不合先来个1
> Task :Two
一言不合先来个2
> Task :Three
我需要1跟2
Three依赖了One, Two,在执行的时候先执行One,Two再执行Three。但是注意一个点,不要被dependsOn: [One, Two] 这样的写法迷惑了,不要以为依赖的执行顺序就是从左到右执行,正确的是依赖的执行顺序是不确定。
task One {
doFirst {
println "一言不合先来个1"
}
}
task Two {
doFirst {
println "一言不合先来个2"
}
}
task Four {
doFirst {
println "一言不合先来个4"
}
}
task Three(dependsOn: [One, Two,Four]) {
doFirst {
println "我需要1跟2跟4"
}
}
终端执行:gradle Three
得到:
> Task :Four
一言不合先来个4
> Task :One
一言不合先来个1
> Task :Two
一言不合先来个2
> Task :Three
我需要1跟2跟4
虽然Four是放在最右边的,但是执行却是第一个执行的。为什么task依赖执行是无序呢?因为这些依赖task执行的时候是并行的,这样在编译构建的时候,并行执行能够极大的节约编译执行时间。
这时候你可能会想,如果需要指定一个task一定要执行在另外一个task前面呢,比如先执行单元测试,再执行打包。
这种需求是正常的,gradle肯定也是考虑到的。要实现task顺序,有两种方式:
- shouldRunAfter/mustRunAfter
taskA.shouldRunAfter(taskB):表示taskB应该在taskA执行后执行。主要这里是should,不是必须要的,也就是还是可能会出现不会按照预设那样执行。
taskA.mustRunAfter(taskB):表示taskB必须在taskA执行后执行。这里是must,一定会按照预设那样执行。
-
finalizedBy
finalizedBy 也是task的终结器。就是指定一个task在另外一个task结束后必须执行。
task A { doLast { print "AAAAAA" } } task B { doLast { print "BBBBB" } } A.finalizedBy B
终端执行:gradle A
Task :A AAAAAA Task :B BBBBB
2.3 生命周期
很多框架跟组件都有自己的生命周期,Gradle也有生命周期。Gradle生命周期分为三个部分:初始化阶段,配置阶段,执行阶段。
-
初始化极端
在初始化阶段,Gradle为每个build.gradle分配一个project实例对象,初始化阶段相关类似setting.gradle
的脚本文件 ,然后根据正在执行的项目,找出哪些项目依赖需要参与到构建中来。
-
配置阶段
初始化阶段结束后紧跟就是配置阶段。配置阶段的任务是执行各项目下的build.gradle脚本,完成Project的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task。 Gradle构造了一个模型来表示任务,并且参与到任务构建中来。增量式构建特性决定了模型中的task是否需要被运行。注意,项目中每一次构建的任何配置代码都可以被执行。
-
执行阶段
在执行阶段,就是将配置阶段task关系依赖顺序进行执行。因为是增量构建,如果一个task如果被认为没有修改过则不会执行。