1.多种方式创建任务:
在Gradle中,我们可以有很多种方式来创建任务。这都依赖于Project给我们提供的快捷方法以及TaskContainer提供的相关onCreate方法。
第一种:直接以一个任务名字创建任务的方式
def Task ex6=task(ex6)
ex6.doLast{
println "创建方法原型为:Task task(String name)"
}
我们可以使用gradle ex6来执行这个任务,这种方式的创建其实是调用Project对象中的task(String name)的方法。
第二种:是以一个人物名字+一个队该任务配置的Map对象来创建
def Task ex7=task(ex7,group:BasePlugin.BUILD_GROUP)
ex7.doLast{
println "创建方法原型为:Task task(Map<String,?> args,String name)"
println "任务分组:${ex7.group}"
}
和第一种方法大同小异,只是多了一个Map参数,用于对要创建的Task进行配置。比如我们例子里为其指定了分组为BUILD,我们通过执行该任务可以看到我们的配置起了作用。Map中可用的配置如下:
第三种:任务名字+闭包配置的方式
task ex8{
description '演示创建'
doFirst{
println "创建方法原型:Task task(String name,Closure configureClosure)"
println "任务描述:${description}"
}
}
因为Map参数配置的方式(第二种)可配置项有限,所以可以通过闭包的方式进行更多灵活的配置。闭包里的委托对象就是Task对象的任何方法、属性等信息进行配置,比如示例中我们配置了任务的描述和任务执行后要做的事情。
下面我们说一下TaskContainer创建任务的方式。如果我们去查看Project对象中关于上面我们演示的Task方法的源代码,就会发现它们最终都是调用TaskContainer对象中的create方法,其参数和Project中的Task方法基本一样。我们使用这种方式重写第三种方式的例子:
tasks.create('ex9'){
description '演示'
doLast{
println "创建方法原型为:Task create(String name,Closure con)"
println "任务描述:${description}"
}
}
tasks是Project对象的属性,其类型是TaskContainer,我们可以使用它来直接创建任务。
2.多种方式访问任务
首先,我们创建的任务都会作为项目(Project)的一个属性,属性名就是任务名,所以我们可以直接通过该任务名称访问和操纵该任务:
task ex10
ex10.doLast{
println 'ex10.doLast'
}
任务都是通过TaskContainer创建的,其实TaskContainer就是我们创建任务的集合,在Project中我们可以通过tasks属性访问TaskContainer,所以我们就可以以访问集合的方式来访问我们创建的任务:
task ex11
tasks['ex11'].doLast{
println 'ex11.doLast'
}
访问的时候,任务名就是Key(关键索引)。其实这里说Key不恰当,因为tasks并不是一个Map。[]在Groovy中是一个操作符,我们知道Groovy的操作符都有对应的方法让我们重载,a[b]对应的是a.getAt(b)这个方法,对应的例子tasks['ex11’]其实就是调用tasks.getAt('ex11')这个方法。如果我们查看Gradle源码的话,最后发现是调用findByName(String name)实现的。
通过路径或者名称访问,有两种方式,一种是get,另一种是find,它们的区别在于get的时候如果找不到该任务就会抛出UnknownTaskException异常,而find在找不到该任务的时候会返回null:
task ex12
tasks['ex12'].doLast{
println tasks.findByPath('ex12')
println tasks.getByName('ex12')
println tasks.findByName('ddd')
}
3.任务分组和描述
任务是可以分组和添加描述的,任务的分组其实就是对任务的分类,便于我们对任务进行整理,这样清晰明了。
def Task myTask=task ex13
myTask.group=BasePlugin.BUILD_GROUP
myTask.description='这是构建引导'
myTask.doLast{
println "group:${group},des:${description}"
}
执行gradle tasks查看任务信息的时候,就能看到该任务已经被归类到Build tasks分类里,并且可以看到该任务的描述信息:
Build tasks
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
ex13 - 这是构建引导
ex7
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
上面输出可以看到,我们刚刚新建的任务被归类到build分组下了,这样我们可以很方便地找到该任务。
4.<<操作符
"<<"操作符在Gradle的Task上是doLast方法的短标记形式,也即是说“<<”可以代替doLast:
task ex14{
println 'ex14'
}
ex14<<{
println "ex14<<"
}
ex14.doLast{
println "ex14DoLast"
}
"<<"操作符在Groovy中是可以重载的,a<<b对应的是a.leftShift(b)方法,所以Task接口中肯定有一个leftShift方法重载了“<<”操作符,查看Task源码:
/**
<p>Adds the given closure to the end of this task's action list. The closure is passed this task as a parameter
when executed. You can call this method from your build script using the << left shift operator.</p>
@param action The action closure to execute.
@return This task.
@deprecated Use {@link #doLast(Closure action)}
*/
@Deprecated
Task leftShift(Closure action);
从注释中我们可以看到,说明了可以使用“<<”操作符。doLast和leftShift有没有区别呢?我们看一下两个方法的实现:
@Override
public Task doLast(final Closure action) {
hasCustomActions = true;
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
taskMutator.mutate("Task.doLast(Closure)", new Runnable() {
public void run() {
getTaskActions().add(convertClosureToAction(action));
}
});
return this;
}
@Override
public Task leftShift(final Closure action) {
DeprecationLogger.nagUserWith("The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0\. Please use Task.doLast(Action) instead.");
hasCustomActions = true;
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
taskMutator.mutate("Task.leftShift(Closure)", new Runnable() {
public void run() {
getTaskActions().add(taskMutator.leftShift(convertClosureToAction(action)));
}
});
return this;
}
上面程序中关键的依据代码是actions.add(),这一句就是我们配置的操作转换为Action放在actions这个List里面,是直接放在末尾,所以它们两个效果是一样的。但是,从leftShit方法中可以看出,leftShift不能直接被调用了,否则会报异常信息。
5.任务的执行分析
当我们执行一个Task的时候,其实就是执行其拥有的actions列表,这个列表保存在Task对象实例中的actions成员变量中,其类型是一个List:
@Override
public List<Action<? super Task>> getActions() {
if (observableActionList == null) {
observableActionList = new ObservableActionWrapperList(getTaskActions());
observableActionList.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
taskMutator.assertMutable("Task.getActions()", evt);
}
});
}
return observableActionList;
}
@Override
public List<ContextAwareTaskAction> getTaskActions() {
if (actions == null) {
actions = new ArrayList<ContextAwareTaskAction>();
}
return actions;
}
现在我们把Task之前执行、Task本身执行以及Task之后执行分别称为doFirst、doSelf和doLast,下面以一个列子来演示:
def Task task15=task ex15(type: CustomTask)
task15.doFirst{
println 'Task执行之前doFirst'
}
task15.doLast{println 'Task执行之后doLast'}
class CustomTask extends DefaultTask{
@TaskAction
def doSelf(){
println 'Task自己在执行doSelft'
}
}
输出:
Task :ex15
Task执行之前doFirst
Task自己在执行doSelft
Task执行之后doLast
例子中我们定义了一种Task类型CustomTask,并声明了一个方法doSelf,该方法被TaskAction注解标注,意思是该方法就是Task本身要执行的方法。
当我们使用Task创建ex15这个任务的时候,Gradle会解析其带有TaskAction标注的方法作为其Task执行的Action,然后通过Task的prependParallelSafeAction方法把Action添加到actions List里。
而foFirst和doLast分别是往actions List的第一个位置和最后一个位置分别添加一个action。所以最后输出的就是doFirst、doSelft、doLast的顺序。
5.“任务排序”
其实并没有真正的任务排序功能,这个排序不像我们想象的通过设置优先级或者Order顺序实现,而是通过任务的shouldRunAfter和mustRunAfter这两个方法,它们可以控制一个任务应该或者一定在某个任务之后执行。通过这种方式你可以在某些情况下控制任务的执行顺序,而不是通过强依赖的方式。
这个功能是非常有用的,比如我们公司自己设置的顺序是,必须先执行单元测试,然后才能进行打包,这就保证了App的质量。
taskB.shouldRunAfter(taskA)表示taskB应该在taskA执行之后执行,这里的应该而不是必须。所以有可能任务顺序并不会按预设的执行。
taskB.mustRunAfter(taskA)表示taskB必须在taskA执行之后执行,这个规则就比较严格了。
task ex16<<{
println 'ex16'
}
task ex17<<{
println 'ex17'
}
gradle ex16 ex17输出:
Task :ex16
ex16
Task :ex17
ex17
当加上这句代码:ex16.mustRunAfter ex17
在执行:gradle ex16 ex17
输出:
Task :ex17
ex17
Task :ex16
ex16
6.任务的禁用和启用
Task中有个enabled属性,用于启用和禁用任务,默认是true,表示启用;设置为false,则禁止该任务执行。
task ex18<<{
println "ex18"
}
ex18.enabled=false
在某些情况下,可以通过该属性灵活控制任务的执行,这种方式需要在执行到具体逻辑的时候才能进行判断设置。
7.任务的onlyIf断言
断言就是一个条件表达式。Task有一个onlyIf方法,它接受一个闭包作为参数,如果该闭包返回true则该任务执行,否则跳过。这个有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。
加入我们的收发渠道是应用宝,执行执行build会编译出来所有包,这个太慢了也不符合需求,现在我们就采用onlyIf的方式通过属性来控制:
final String ALL="all"
final String SHOUFA="shoufa"
final String EXC_SHOUFA="exclude_shoufa"
task exQQ<<{
println "应用宝包"
}
task exBaidu<<{
println "百度包"
}
task build{
group BasePlugin.BUILD_GROUP
description "打渠道包"
}
build.dependsOn exQQ,exBaidu
exQQ.onlyIf{
def execute=false;
if(project.hasProperty("build_apps")){
Object buildApps=project.property("build_apps")
if(SHOUFA.equals(buildApps)||ALL.equals(buildApps)){
execute=true
}else{
execute=false
}
}else{
execute=true
}
execute
}
exBaidu.onlyIf{
def execute=false;
if(project.hasProperty("build_apps")){
Object buildApps=project.property("build_apps")
if(EXC_SHOUFA.equals(buildApps)||ALL.equals(buildApps)){
execute=true
}else{
execute=false
}
}else{
execute=true
}
execute
}
打包所有渠道包
gradle -Pbuild_apps=all build
打首发包
gradle -Pbuild_apps=shoufa build
打非首发包
gradle -Pbuild_apps=exclude_shoufa build
通过上面3个命令就可以动态控制我们要打那些渠道包。
命令行中-P的意思是为Project指定K-V格式的属性键值对,使用格式为-PK=V。
8.任务规则
我们想获取一个TaskContainer中名为taskObject的元素,但这个任务可能不存在,这个时候我就就需要添加规则来处理这种异常情况了:
tasks.addRule("测试的一个用例"){String taskName->
task(taskName)<<{
println "该${taskName}任务不存在"
}
}
执行:gradle aa
输出:
Task :dd
该dd任务不存在