前几天我们讲解了groovy的一些源码,也讲解了gradle的一些内容,我们知道gradle是groovy的一个重要的使用场景,那就是自动化构建,gradle里有许多核心的概念,比如Project,Dependency,Plugin,Setting,File等等。但,要说到最重要的一个点,那必须是gradle的Task,因为无论我们在Task之前写了多少代码,如果没有Task,那一切都是没有意义的,所以Task就是我们本节教程重点要讲解的。今天的课程我们会重点讲解Task的以下几点。
1.如何定义一个新的Task
2.如何为已有的Task添加执行逻辑
3.Task执行的两个不同生命周期
4.Task的执行顺序
5.如何自定义Task
下面,我们就依次来讲解一下这几部分的。
首先,如何定义一个Task呢,很简单,Project这个类中已经为我们提供了现成的API,我们只需要调用我们想用的那个即可,下面,我们就用代码来具体看一下Task的定义
//最简单的定义方式,定义一个名字为hello的task,并为这个task传入了一个闭包作为参数。
task hello {
print 'hello world'
}
//我们还可以定义的时候,为Task指定他的类型,分组等一系列配置。这样我们的Task就继成于了Copy这个已经存在的Task,并且分在了Test这个组中。
task hello(type: Copy, group: 'Test') {
print 'hello world'
}
//当然我们还可以将分组等属性的定义放在闭包体中去。如下:
task hello(type: Copy) {
group 'Test'
from 'src/main/doc'
into 'build/target/doc'
}
//其实就是调用了Task类内部的setGroup()方法,以及从Copy类继成下来的from方法和into方法。
以上就是Task的几种最常用的定义方法,定义好以后,我们就可以在控制台下通过./gradlew hello来执行这个定义好的Task.Task的定义非常的简单。
但不是所有的时候都需要去定义一个新的Task,有时候我们可能只是想为已经有的Task去添加一些逻辑,要怎么做呢,很简单呀,找到已经定义过的Task,然后为其添加行为即可,下面我们来实际看一下如何为一个已经存在的Task添加逻辑。上面我们已经创建好了一个名为hello的Task,假设我们不能直接修改这个task的内容,我们要怎么做呢?
//首先,通过TaskContainer类的实例tasks去找到我们想要添加逻辑的那个Task
def helloTask = tasks.findByPath('hello')
//如果有这个Task
if(helloTask != null){
//通过doLast为已存在Task添加逻辑,也可以使用doFirst为Task的最开始添加逻辑
helloTask.doLast{
print 'the mseeage is add by doLast method'
}
}
看起来很简单,但是这个使用场景在实际开发中是很有用的,尤其是在我们想要扩展一些第三方Task的时候,我们没有第三方Task的代码,所以,我们只需要知道他的名字就能为其它添加一些我们想要的逻辑。就拿上面我们提到的Copy这个已经存在的Task,如果我们想要每次复制内容的时候,生成一条操作记录,我们就可以扩展Copy这个Task,拿到src和destation,然后将这条记录创建并保存到文件或者数据库中就可以完成这个功能。
第三,我们来看一下Task的涉及到的两个不同的生命周期,有对Project生命周期不了解的,可以看我以前的文章,或者通过学习我的gradle实战课程来系统的学习。
大家来看下面的代码:
task hello {
doLast{
print "数据已成功存入文件."
}
doFirst{
print "记录已正确生成."
}
print '文件拷贝完成.'
}
大家觉得,这个task执行以后,这三条语句的输出会是什么样子的呢,从代码定义的先后顺序来看,会觉得先输出doLast中的内容,再输出doFirst中的内容,最后输出‘文件拷贝完成.’这句话,但如果大家真正的执行一下以后,就会发现顺序是完全相反的,这是为什么呢,这就涉及到了,我们的Task最特殊的一个地方,他会涉及到2个不同的生命周期,配置期和执行期,而配置期的代码永远是先于执行期代码执行的,由于前两句代码在doLast和doFirst方法中,所以,这两名代码的执行是在执行期,因此总是后于最后一句的输出。而doFirst的内容又总是先于doLast执行,所以我们最终正确的输出其实是:
文件拷贝完成.
记录已正确生成.
数据已成功存入文件.
gradle的生命周期是gradle比较复杂的一个点,所以不明白的同学还是建议大家去系统的学习一下我的gradle实战课程去学习
前面,我们分别讲解了Task的定义,查找,以及Task中的代码所涉及到的两个生命周期,下面,我问大家一个问题,假如我们有许多个task,我希望这些Task在执行的时候,能够按照我们指定的执行顺序去执行,该怎么办呢,不用担心,Task已经为我们提供了各种各样的方法,我们要以通过调用task的dependsOn方法,为我们的task添加一个依赖,通过强依赖的方法,可以让我们当前的Task执行于被依赖的Task之后,代码如下:
task taskA{
print 'i am is task A'
}
task taskB{
print 'i am is task B'
}
//通过让taskB依赖于taskA,这样taskB在执行的时候就一定会先执行taskA
taskB.dependsOn taskA
虽然通过依赖的方式,可以指定两个task的执行顺序,但是也带来了问题,就是这两个task强耦合了,我们能不能即不让这些task强耦合,又能按照我们想要的顺序呢,当然是可以的,这时候我们需要使用其它的方法,代码如下:
task taskA{
print 'i am is task A'
}
task taskB{
print 'i am is task B'
}
//能过mustRunAfter可以使B在A之后执行,但有一个前提是A和B都会执行到,如果我们只执行了B,那与A是没有任何关系的。
taskB.mustRunAfter taskA
Task类还有一个方法是shouldRunAfter,与mustRunAfter用法基本一致,只是没有那么强的关系。通过结合使用dependsOn和mustRunAfter就可以规范好所有task的执行顺序。
最后,我们再来看一下如何自定义一个Task,自定义一个Task非常的简单,代码如下:
//1.继成于DefaultTask,此类就是一个Task了
class ChannelPackageTask extends DefaultTask {
public static final int DEFAULT_MODE = -1
public static final int V1_MODE = 1
public static final int V2_MODE = 2
@Input
public boolean isMergeExtensionChannelList = true
@Input
public List<String> channelList
//2.被TaskAction修饰的方法,即执行于执行阶段的代码
@TaskAction
void channel() {
checkParameter();
checkSigningConfig()
generateChannelApk()
}
protected mergeExtensionChannelList(){
List<String> extensionChannelList = getExtensionChannelList()
if (extensionChannelList != null && !extensionChannelList.isEmpty()){
if (channelList == null){
channelList = extensionChannelList
}else {
channelList.addAll(extensionChannelList)
}
}
}
}
所以大家可以看到定义一个Task,只需上面两步即可,非常的简单,你只需要把你要实现的逻辑放在被TaskAction修饰的方法中即可,定义好以后,就可以在我们的脚本中直接使用。
好,这就是我们今天要讲的Task的一些核心功能点,看起来非常的简单,但在实际开发中,是有许多地方要注意的,大家可以下来多加练习一下Task的定义,如果想了解Task相关的更多实战,可以学习我的gradle实战课程来系统学习。