Android Gradle Task详解

创建一个task

创建一个task很简单,直接使用task关键字来创建,最常见的形式是使用task + 名字 + Closure代码块。示例如下。

task ms1 {
    println("=================task ms1 added=================")
}

也可以只有task + 名字,就像下面这样。当然这样的task什么也不做。

task myTask

创建task经常还会看到另外一种写法,在名字后面加上 << 符号。

task hello << {
    println 'Hello world!'
}

这种写法和下面的写法是等同的,也就是在创建task的时候为其添加了一个action到末尾。不过这种写法目前已被标记为deprecated,预期会在Gradle 5.0版本中移除,所以最好不要再使用了。

task hello {
    doLast {
        println 'Hello world!'
    }
}

上述几种写法都有一个共同点,就是创建task的时候必须指定一个名字,每个task都需要有一个名字,所有的task在创建之后都隶属于某个Project。同一个Project中任意两个task的名字不能相同。

此外,添加task不一定要在build.gradle的最外层,可以放在任意的代码块中,只是在执行时机上有所区别。

如下代码将task hello放到了allprojects()中执行,它会为项目中每一个project创建一个名为hello的task。

allprojects {
    task hello
}

如下代码则在循环中创建了task0,task1,task2,task3四个不同的task。

4.times { index ->
    task "task$index" {
        println "I'm task number $index"
    }
}

创建task的时候还可以指定一些参数。比较重要的有:

  1. type: task类型
  2. dependsOn:依赖的task列表
  3. group:task所属的组

其他参数可以参见 https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskContainer.html#create-java.util.Map-

为task添加Action

每个task中可以包含0到多个Action,这些Action保存在一个ArrayList成员变量中,当执行这个task时,会按照顺序依次执行所有的Action。

要为task添加Action,可以通过task的doFirst()和doLast()方法来实现。doFirst()方法会将Action添加到ArrayList的开头,而doLast()方法则将Action添加到ArrayList的末尾。可以有多个doFirst()和doLast(),配置task时会按照它们出现的先后顺序添加到Action列表中。

创建task的同时添加Action

可以在创建task的同时添加Action,只需要将其添加到创建task的闭包中即可。

以下代码在创建ms1 task的时候为它添加了4个action。

task ms1 {
    doFirst {
        println("=================ms1 doFirst1=================")
    }
    doFirst {
        println("=================ms1 doFirst2=================")
    }
    doLast {
        println("=================ms1 doLast1=================")
    }
    doLast {
        println("=================ms1 doLast2=================")
    }
}

-- 执行这个task会产生如下输出 --
=================ms1 doFirst2=================
=================ms1 doFirst1=================
=================ms1 doLast1=================
=================ms1 doLast2=================

为现有task增加Action

也可以为已有的task增加Action。

如下代码为build task增加了两个Action,如果其他没有对build task再做修改,那么这里添加的doFirst会作为build task的第一个Action最先被执行,而doLast则会作为build task的最后一个Action,在其他Action执行完成后再执行。

build {
    doFirst {
        println("=================build doFirst=================")
    }
    
    doLast {
        println("=================build doLast=================")
    }
}

如下代码为ms1 task添加了一个新的action,这个action被添加在了其他action的前面。

ms1.doFirst {
    println("=================ms1 doFirst=================")
}

为动态创建的task增加Action

有些task是动态创建的,无法直接在build.gradle的最外层为其增加Action,又或者需要在task定义代码之前为其添加Action,这时可以将其放到Project.afterEvaluate()方法中去执行。

afterEvaluate {
	assembleDebug {
	    doLast {
	        println("=================assembleDebug doLast=================")
	    }
	}
}

或者可以在Task creation事件的回调中去添加。

tasks.whenTaskAdded { task ->
    if (task.name == 'assembleDebug') {
      task.doFirst {
        println("=====================assembleDebug doFirst=========================")
      }
    }
}

从上面的三小节可以看出,添加action可以出现在代码任意地方,只要拿到对应的task就可以为其添加action。需要注意的是不能为正在运行的task添加action,否则会抛出异常。例如如下代码为myTask添加了一个action,在这个action执行的时候再为myTask添加另一个action,这时就会有运行时异常,但可以在一个task执行的时候为其他task添加action,这里将myTask改成任意其他的task名字是可以正常运行的。

myTask.doFirst {
    println("=================ms1 doFirst=================")
    myTask.doFirst {
        println("=================add another action=================")
   }
}

指定Action名字

从Gradle 4.2版本开始支持在调用doFirst和doLast的时候为Action指定一个名字,不过这个特性仍然是Incubating状态,也就是试验阶段。关于Incubating状态的说明参见 https://docs.gradle.org/current/userguide/feature_lifecycle.html#sec:states

关于actionName这个参数是这样描述的。

actionName - An arbitrary string that is used for logging.

也就是说这个名字应当只用在打印log的时候。

添加task之间的依赖

task提供了dependsOn,finalizedBy方法来管理task之间的依赖关系,依赖关系表达的是执行这个task时所需要依赖的其他task,也就是说这个task不能被单独执行,执行这个task之前或之后需要执行另外的task。

在一个task前执行另一个task

要在一个task之前执行另一个task,可以通过配置dependsOn的依赖关系实现。

例如有两个task:taskA和taskB,通过指定taskA.dependsOn taskB就可以让在执行taskA之前先执行taskB,或者指定taskB.dependsOn taskA就可以让在执行taskB之前先执行taskA。

在一个task后执行另一个task

要在一个task之后执行另一个task,无法通过配置dependsOn实现。要实现这个功能需要通过finalizedBy来实现。

同样的有两个task:taskA和taskB,通过指定taskA.finalizedBy taskB就可以让在执行taskA之后执行taskB,或者指定taskB.finalizedBy taskA就可以让在执行taskB之后先执行taskA。

在一个task中执行另一个task

gradle在执行一组task的时候会根据他们的依赖关系生成一个task序列,然后按照序列的先后顺序来依次执行各个task。单个task的执行一定是一个原子过程,gradle不允许在一个task中执行另一个task,因此不会出现嵌套执行的情况。也就是说所有的task执行都是由gradle自身去调度,只有执行完一个task之后才会去执行下一个task,不会出现task没有执行完就转去执行另一个task中的任务的情况。

值得一提的是,现有gradle版本中为task保留了一个execute()方法来执行一个task,这个方法已被标记为deprecated,不过还是可以用的。

如下代码看起来是在ms2 task的最后一个action中去执行ms1 task,但实际上在执行gradlew ms2的时候,仍然会先执行ms1,然后再执行ms2,并不会出现执行ms2到最后一个action的时候,再转去执行ms1。所以它和添加ms2到ms1的依赖没有区别,鉴于这种用法已被标记为deprecated,且过程难以被理解,所以最好就不要用execute()了。

task ms2 {
  doLast {
    println("=====================ms2.=========================")
    ms1.execute()
  }

}

task ms1 {
  doLast {
    println("=====================ms1.=========================")
  }
}

tasks队列

在通过dependsOn和finalizedBy设定好task之间的依赖关系后,在执行一个task时就会根据依赖关系生成一个task队列。

例如,有四个task,task0,task1,task2,task3。指定依赖关系如下。

task0 dependsOn task1
task1 dependsOn task2
task0 finalizedBy task3

当执行gradlew task0时就会先生成 task2 -> task1 -> task0 -> task3这样一个序列,然后按照顺序来执行。

给tasks排序

在上述例子中,执行task0得到的tasks队列中每个task的位置都是明确的,它们的先后顺序也是完全确定的。然而更多的情况是,一个任务队列中有部分task的位置不是很明确。

例如,有四个task,task0,task1,task2,task3。指定依赖关系如下。

task0 dependsOn task1
task0 dependsOn task2
task0 finalizedBy task3

按照依赖关系,执行task0生成的任务队列可以是task2 -> task1 -> task0 -> task3,也可以是task1 -> task2 -> task0 -> task3,可以看到task2和task1之间的顺序是不确定的。虽然gradle总是可以按照一定的规则(例如task名字的字典序)来得到一个固定的task队列,但有时我们需要人为明确这个顺序,例如这里我们希望task2总是在task1之前执行。这时有两种方案,一种是使用dependsOn,我们让task1 dependsOn task2,这样task2一定会在task1之前执行,但这种方案会让单独执行task1的时候也会先执行task2,这有可能和实际需要不符。这时就需要使用另一种方案,通过shouldRunAfter和mustRunAfter来指定这个顺序。

shouldRunAfter和mustRunAfter表达的都是一个任务需要在另一个任务之后执行,它们的区别是mustRunAfter要求gradle生成任务队列时必须确保这个顺序一定要满足,如果不能满足就会报错,shouldRunAfter相当于建议gradle按照这顺序来生成任务队列,gradle会优先参考其他约束条件,如果在满足其他约束条件后,这条约束也能满足,那么就采纳这个顺序,如果不能满足,就忽略这个请求。

例如有依赖关系task0 dependsOn task1,这时又指定task1 mustRunAfter task0,显然这时这个条件无法被满足,因此执行task0的时候会报错。但如果指定task1 shouldRunAfter task0,则不会有错误,gradle会自动忽略掉这条请求。

再来看上述例子
task0 dependsOn task1
task0 dependsOn task2
task0 finalizedBy task3

这时无论指定task1 shouldRunAfter task2,还是task1 mustRunAfter task2,都会得到任务队列task2 -> task1 -> task0 -> task3。

需要注意的是,无论是shouldRunAfter还是mustRunAfter,影响的只是task在队列中的顺序,并不影响任何任务间的执行依赖,也就是说使用shouldRunAfter和mustRunAfter并不会导致任务队列中添加新的task。

例如指定ms1 mustRunAfter ms2,如果ms1和ms2没有其他依赖关系,那么在执行ms1的时候并不会先执行ms2,执行ms2之后也不会执行ms1。

预定义的gradle task

gradle为每个工程提供了很多预先定义好的task,通过这些task我们可以不需要手动创建任何task就可以实现编译,打包,测试等功能。在前文中已经出现的clean build等就是gradle中预定义的task。在做Android开发时,Android Gradle插件也提供了一组预定义的gradle task,当我们新建一个Android工程就能看到如下所示的task列表,这里的tasks都是gradle和Android Gradle Plugin为我们创建好的,可以直接使用。

在这里插入图片描述

清理工程(clean task)

clean task用来清理上次编译的缓存内容

编译相关task

和编译相关的task主要有:build和assemble,其中build依赖assemble,也就是说执行build之前会先执行assemble。在Android上,会根据buildType和productFlavor的不同自动创建多个assembleXxx任务,如assembleDebug,assembleRelease等,assemble会依赖所有的assembleXxx任务,也就是说执行assemble会先执行assembleDebug,assembleRelease等一系列的assemble任务。

在编译完成后执行一段代码

有时我们需要在编译任务完成后执行某一个任务,按照通常的做法,我们创建一个task,然后将这个task放到编译的task之后执行。

如前所述要在一个task之后执行另一个task,可以通过finalizedBy来实现,但是编译任务对应的task很多,有些还是动态创建的,如果每个任务都指定一遍finalizedBy会很麻烦。好在gradle提供了一个buidlFinished()方法,它可以在编译完成后执行一定的操作,不用理会当前执行的是哪个编译task,只需要把task中代码移到buidlFinished中即可。

project.gradle.buildFinished {
    println "build finished"
}

和buildFinished()对应的还有一个buildStarted()方法,它可以在编译开始之前执行需要的代码。

task相关属性

获取当前task的名字

要在当前任务里获取自己的名字,可以使用其name属性

task ms1{
    doLast {
        println name			// ms1
    }
}

获取当前action的名字

如前所述,action的名字应当只用在内部记录log的时候,目前没有找到方法获取到这个名字。

获取当前运行的gradle任务列表

在gradle脚本中可以通过project.gradle.startParameter.taskNames来获取本次执行的gradle task列表。它得到的是一个数组,即使只执行了一个task。如果没有指定运行任何task,则返回空的数组。

-- build.gradle --
println project.gradle.startParameter.taskNames

-- 命令行 --
> gradlew clean build

-- 输出 --
[clean, build]

自定义任务属性

可以在任务配置代码中,通过ext.xxx来为任务添加自定义属性。

例如

task myTask {
    ext.myProperty = "myValue"
}

定义好之后就可以在其他代码中通过myTask.myProperty来访问这个属性的值。

默认任务

Gradle 允许在build.gradle脚本中定义一个或多个默认任务,默认任务的意思是没有指定任何任务的时候会执行默认任务。要指定默认任务可以使用defaultTasks关键字。

例如,如下代码指定默认任务为myTask和build,则执行gradlew时如果没有指定任何任务,就会执行myTask和build。

defaultTasks 'myTask', 'build'

task myTask {
    doLast {
        println "run default task."
    }
}

需要注意的是,使用defaultTasks指定默认任务的时候,任务名一定要加引号,即使它是在任务定义之后声明,也是如此。

覆盖已有的任务

有时我们可能需要覆盖掉一个已有的任务,这个任务可能是自定义的,或者是来自其他Gradle插件中的任务。虽然这种场景并不常见,不过还是有必要了解这种用法,说不定在需要的时候就能帮上大忙。

覆盖已有任务只需要重新创建一个新的同名task,并指定overwrite为true即可。

overwrite的覆盖操作,不仅会覆盖掉原先task的所有action,而且会覆盖原先task指定的type类型,还会覆盖掉所有和原先task相关的依赖关系。被覆盖的依赖关系不仅包含原先的task对其他task的依赖,还包含其他task对原先task的依赖,所有的和原先task相关的依赖关系都会被移除。当然,可以在overwrite之后重新定义新的依赖关系。

overwrite的覆盖操作不会覆盖原先创建task块中的代码,所有创建task代码仍然会被执行。

此外,如果定义overwrite的task在这之前并没有被创建,那么gradle会忽略这个属性,等同于创建一个新的task,不会有错误出现。

如下代码创建了两个名为copy的task,第二个copy task指定了overwrite为true,因此第二个copy task会覆盖第一个。此外,第一个copy task依赖一个名为delete的task,由于这条依赖关系在overwrite之前配置,所以同样会被覆盖。

task delete(type: Delete) {
    delete "s1.txt"
}

task copy(type: Copy) {
    println name
    from(file('ss.txt'))
    into(file('new_dir'))
    doLast {
        println "I am a copy task"
    }
}

copy.dependsOn delete

task copy(overwrite: true) {
    println name
    doLast {
        println('I am the new one.')
    }
}

这里执行gradlew copy的完整流程如下。

  1. 创建delete task,执行语句delete “s1.txt”,注意这里只是执行了delete()方法指定了要删除的文件,并没有真正执行删除操作,删除操作只有执行这个task的时候,才会在delete task的action中执行。
  2. 创建第一个copy task,依次执行所有代码,即println name, from(file(‘ss.txt’)),into(file(‘new_dir’))和doLast。同样的这里的doLast只是为task添加了action,并不是真正的执行了这个action。
  3. 执行copy.dependsOn delete为copy添加一个依赖关系。
  4. 创建第二个copy task,由于overwrite为true,所以会移除原先的copy task,原先copy task所有信息都会丢失,包括依赖关系,包括指定的type,也包括 from(file(‘ss.txt’)),into(file(‘new_dir’))的配置和doLast添加的action。但是println name由于已经执行过了,已经打印出来的内容不会消失不见。然后重新创建新的copy task,执行println name和doLast。
  5. 执行copy task,由于原先task已经不存在,所以只会执行println(‘I am the new one.’)这个action中的语句。不会执行println “I am a copy task”,也不会执行复制’ss.txt’到new_dir的操作,也不会执行delete task。最终结果就是打印了两次name和一次I am a copy task。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页