我正在参加「启航方案」
1、前言
本文是Gradle系列的第7篇,给咱们带来Gradle构建中心Task
相关的知识点。
2、Gradle中的Task是什么
Task
是一个使命,是Gradle
中最小的构建单元
。
Gradle构建的中心便是由Task组成的有向无环图:
Task是Gradle构建的中心目标,Task能够接纳输入、履行某些操作,并依据履行的操作产生输出。
Task管理了一个Action
的List,你能够在List前面刺进一个Action(doFirst),也能够从list后边刺进(doLast),Action是最小的履行单元
。
3、怎样创立一个task
3.1、Task写在哪
首先想一下Task写在哪?
咱们在Gradle系列的第4篇生命周期中介绍到,有三个阶段,第一个阶段初始化会决议哪些项目参与编译,第二个阶段便是解析装备,会生成Task注册表(DAG),第三个阶段便是依次履行Task。
反历来推,履行Task需求一个Task注册表,Task的来历需求先决议哪些项目参与编译,也便是说Task注册表是由参与编译的项目决议的,即能够了解为Task是由Project
目标决议的,所以Task的创立是在Project中,一个build.gradle文件对应一个Project目标,所以咱们能够直接在build.gradle文件中创立Task。
只需运转的上下文在Project中就能够。
3.2、创立Task
创立Task需求运用TaskContainer的register办法。
register的几种办法:
- register(String name, Action<? super Task> configurationAction)
- register(String name, Class type, Action<? super T> configurationAction)
- register(String name, Class type)
- register(String name, Class type, Object… constructorArgs)
- register(String name)
比较常用的是1和2。
- configurationAction指的是Action,也便是该Task的操作,会在编译时履行;
- type类型指的是Task类型,能够是自界说类型,也能够指定自带的Copy、Delete、Zip、Jar等类型;
咱们能够直接在build.gradle文件中创立一个task:
tasks.register("yechaoa") {
println("Task Name = " + it.name)
}
上面调用的便是register的办法1,最终一个参数假如是闭包,能够写在参数外面。
上面task咱们是经过TaskContainer(tasks)创立的,在Project目标中也供给了创立Task的办法,写法上有一点差异:
task("yechaoa") {
println "aaa"
}
经过Plugin的办法也能够创立Task,重写的apply办法会有Project目标。
4、如何履行Task
4.1、履行单个Task
指令:
./gradlew taskname
示例:
tasks.register("yechaoa") {
println("Task Name = " + it.name)
}
履行:
./gradlew yechaoa
输出:
Task Name = yechaoa
4.2、履行多个Task
./gradlew taskname taskname taskname
task之间用空格分隔。
4.3、Task同名
假如有两个同名的Task,则会编译失败,即InvalidUserDataException
* What went wrong:
A problem occurred evaluating project ':app'.
> Cannot add task 'yechaoa' as a task with that name already exists.
4.4、Task履行成果
咱们经常会在编译时看到Task后边有一个标签,它表明Task的履行成果。
> Task :app:createDebugVariantModel UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:compileDebugRenderscript NO-SOURCE
> Task :app:generateDebugBuildConfig UP-TO-DATE
.....
4.4.1、EXCUTED
表明Task履行,常见。
4.4.2、UP-TO-DATE
> Task :app:preBuild UP-TO-DATE
它表明Task的输出没有改动。
分几种状况:
- 输入和输出都没有改动;
- 输出没有改动;
- Task没有操作,有依靠,但依靠的内容是最新的,或许越过了,或许复用了;
- Task没有操作,也没有依靠;
4.4.3、FOME-CACHE
字面意思,表明能够从缓存中复用上一次的履行成果。
4.4.4、SKIPPED
字面意思,表明越过。
比方被排除:
$ gradle dist --exclude-task yechaoa
4.4.5、NO-SOURCE
Task不需求履行。有输入和输出,但没有来历。
5、Task的Action
5.1、Action
Task的Action便是编译时所需的操作,它不是一个,它是一组,即能够有多个。
多个Task一般是咱们在自界说的时分运用。
5.1.1、自界说Task
class YechaoaTask extends DefaultTask {
@Internal
def taskName = "default"
@TaskAction
def MyAction1() {
println("$taskName -- MyAction1")
}
@TaskAction
def MyAction2() {
println("$taskName -- MyAction2")
}
}
- 自界说一个类,承继自
DefaultTask
; - Action的办法需求添加
@TaskAction
注解; - 对外露出的参数需求运用
@Internal
注解;
运用自界说Task:
tasks.register("yechaoa", YechaoaTask) {
taskName = "我是传入的Task Name "
}
类型传入自界说Task类。
履行成果:
> Task :app:yechaoa
我是传入的Task Name -- MyAction1
我是传入的Task Name -- MyAction2
假如是Action办法的构造函数传参,参数写在type类型后边即可:
tasks.register('yechaoa', YechaoaTask, 'xxx')
5.2、doFirst
归于Action的一种,在Task Action的头部履行。能够有多个。
5.3、doLast
归于Action的一种,在Task Action的尾部履行。能够有多个。
示例代码:
tasks.register("yechaoa") {
it.doFirst {
println("${it.name} = doFirst 111")
}
it.doFirst {
println("${it.name} = doFirst 222")
}
println("Task Name = " + it.name)
it.doLast {
println("${it.name} = doLast 111")
}
it.doLast {
println("${it.name} = doLast 222")
}
}
履行成果:
Task Name = yechaoa
> Task :app:yechaoa
yechaoa = doFirst 222
yechaoa = doFirst 111
yechaoa = doLast 111
yechaoa = doLast 222
Task Name的输出是在Gradle生命周期的装备阶段,由于它就在闭包下面,不在任何Action里,没有履行机遇,装备阶段解析到这个Task就会履行println。
其他输出都是在Task :app:yechaoa下,由于有明确的Action履行机遇。
5.4、Action履行次序
Action的履行次序也是多数人的一个误区,能够经过上面的日志,咱们梳理一下:
- doFirst:倒序
- Action:正序
- doLast:正序
6、Task特点
Task的特点有以下几个:
String TASK_NAME = "name";
String TASK_DESCRIPTION = "description";
String TASK_GROUP = "group";
String TASK_TYPE = "type";
String TASK_DEPENDS_ON = "dependsOn";
String TASK_OVERWRITE = "overwrite";
String TASK_ACTION = "action";
特点装备比较好了解,望文生义。
假如你的IDEA带了Gradle可视化管理的话,比方Android Studio,这样就能够在右侧Gradle面板菜单中找到咱们自界说的Task,双击即可履行。
7、Task依靠
Gradle默许现已有一套Task的构建流程了,你假如想在这个流程参加自界说Task或许在某个自带Task的前后做些切面编程的事,那就需求对Task的依靠联系有所了解。
7.1、dependsOn
tasks.register("yechaoa111") {
it.configure {
dependsOn(provider {
tasks.findAll {
it.name.contains("yechaoa222")
}
})
}
it.doLast {
println("${it.name}")
}
}
tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
履行:
./gradlew yechaoa111
输出:
> Task :app:yechaoa222
yechaoa222
> Task :app:yechaoa111
yechaoa111
界说了dependsOn的Task yechaoa111在目标Task yechaoa222之后履行。
其实相关于上面的写法,dependsOn更常见的写法是这种:
def yechaoa111 = tasks.register("yechaoa111") {
it.doLast {
println("${it.name}")
}
}
def yechaoa222 = tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
yechaoa111.configure {
dependsOn yechaoa222
}
dependsOn依靠的Task能够是称号也能够是path
也能够是一个type类型:
dependsOn tasks.withType(Copy)
假如是其他项目(Project)的task也能够:
dependsOn "project-lib:yechaoa"
7.2、finalizedBy
为Task添加指定的终结器使命。也便是指定下一个履行的Task,dependsOn指定的是上一个。
task taskY {
finalizedBy "taskX"
}
这儿表明taskY履行之后履行taskX。
假如finalizedBy换成dependsOn,则表明taskY履行前要先履行taskX。
7.3、mustRunAfter
def yechaoa111 = tasks.register("yechaoa111") {
it.doLast {
println("${it.name}")
}
}
def yechaoa222 = tasks.register("yechaoa222") {
it.doLast {
println("${it.name}")
}
}
yechaoa111.configure {
mustRunAfter yechaoa222
}
履行:
./gradlew yechaoa111
输出:
> Task :app:yechaoa111
yechaoa111
能够看到yechaoa222并没有履行,由于咱们是独自履行的yechaoa111,要检查依靠联系得一同履行才干看出先后次序。
咱们再来一同履行一次:
./gradlew yechaoa111 yechaoa222
输出:
> Task :app:yechaoa222
yechaoa222
> Task :app:yechaoa111
yechaoa111
能够看到mustRunAfter收效了,yechaoa111在yechaoa222之后履行。
7.4、shouldRunAfter
yechaoa111.configure {
shouldRunAfter yechaoa222
}
shouldRunAfter与mustRunAfter的写法共同。
mustRunAfter是「必须运转」,shouldRunAfter是「应该运转」。
如taskB.mustRunAfter(taskA),当taskA和taskB同时运转时,则taskB必须一直在taskA之后运转。
shouldRunAfter规矩相似,但不太相同,由于它在两种状况下会被疏忽。首先,假如运用该规矩会引进一个排序周期;其次,当运用并行履行时,除了“应该运转”使命外,使命的一切依靠项都已满意,那么不管其“应该运转”依靠项是否已运转,都将运转此使命。
8、越过Task
Gradle供给了多钟越过Task的办法。
- 条件越过
- 反常越过
- 禁用越过
- 超时越过
8.1、条件越过
Gradle供给了onlyIf(Closure onlyIfClosure)
办法,只需闭包的成果返回True时,才履行Task。
tasks.register("skipTask") { taskObj ->
taskObj.configure {
onlyIf {
def provider = providers.gradleProperty("yechaoa")
provider.present
}
}
taskObj.doLast {
println("${it.name} is Executed")
}
}
履行:
./gradlew skipTask -Pyechaoa
输出:
> Task :app:skipTask
skipTask is Executed
只需在脚本指令里边加上了-Pyechaoa
参数,才会履行skipTask。
举一反三,只需onlyIf闭包成果为True即可,条件自定。
8.2、反常越过
假如onlyIf不满意需求,也能够运用StopExecutionException
来越过。
StopExecutionException归于反常,当抛出反常的时分,会越过当时Action及后续Action,即越过当时Task履行下一个Task。
tasks.register("skipTask") { taskObj ->
taskObj.doFirst {
println("${it.name} is Executed doFirst")
}
taskObj.doLast {
def provider = providers.gradleProperty("yechaoa")
if (provider.present) {
throw new StopExecutionException()
}
println("${it.name} is Executed doLast")
}
}
输出:
> Task :app:skipTask
skipTask is Executed doFirst
假如脚本指令里边加上了-Pyechaoa
参数,则会抛反常越过。
可是该Task中之前的Action会被履行到,比方示例中的doFirst。
8.3、禁用越过
每个Task 都有一个enabled
开关,true开启,false禁用,禁用之后任何操作都不会被履行。
tasks.register("skipTask") { taskObj ->
taskObj.configure {
enabled = true
}
taskObj.doLast {
println("${it.name} is Executed")
}
}
8.4、超时越过
Task供给了timeout
特点用于限制履行时刻。
假如Task的运转时刻超过指定的时刻,则履行该使命的线程将被中止。
默许状况下,使命从不超时。
示例:
tasks.register("skipTask") { taskObj ->
taskObj.configure {
timeout = Duration.ofSeconds(10)
}
taskObj.doLast {
Thread.sleep(11 * 1000)
println("${it.name} is Executed")
}
}
输出:
> Task :app:skipTask FAILED
Requesting stop of task ':app:skipTask' as it has exceeded its configured timeout of 10s.
---Gradle:buildFinished 构建完毕了
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:skipTask'.
> Timeout has been exceeded
履行反常了,并提示timeout of 10s,由于我在doLast中sleep了11s。
9、Task增量构建
增量构建
是当Task的输入和输出没有改变时,越过action的履行,当Task输入或输出发生改变时,在action中只对发生改变的输入或输出进行处理,这样就能够避免一个没有改变的Task被反复构建,当Task发生改变时也只处理改变部分,这样能够进步Gradle的构建效率,缩短构建时刻。
任何构建东西的一个重要部分是能够避免做现已完结的工作。编译源文件后,除非发生影响输出的某些改变,例如修正源文件或删去输出文件,不然无需重新编译它们。编译或许需求很多时刻,因而在不需求时越过过程能够节约很多时刻。
Gradle供给这种开箱即用的增量构建的功用,当你在编译时,Task在控制台输出中标记为UP-TO-DATE
,这意味着增量构建正在工作。
下面来看看增量构建是如何工作的以及如何确保Task支撑增量运转。
9.1、输入和输出
一般状况下,使命需求一些输入并生成一些输出。咱们能够将Java编译过程视为Task的一个示例,Java源文件作为Task的输入,而生成的类文件,即编译的成果,是Task的输出。
输入的一个重要特征是,它会影响一个或多个输出,如上图,依据源文件的内容和您要运转代码的Java运转时的最低版别,会生成不同的字节码。
编写Task时,需求告知Gradle哪些Task特点是输入,哪些是输出。 假如Task特点影响输出,请务必将其注册为输入,不然当它不是时,Task将被视为最新状况。相反,假如特点不影响输出,请不要将特点注册为输入,不然Task或许会在不需求时履行。还要留意或许为完全相同的输入生成不同输出的非确定性Task,这些Task不应装备为增量构建,由于UP-TO-DATE检查将不起作用。
上面的两段理论摘自官方,关于新手来说,或许有点晦涩难懂,下面会带咱们实操一下。
9.2、增量构建的两种形式
- 第一种,Task完全能够复用,输入和输出都没有任何改变,即UP-TO-DATE;
- 第二种,有部分改变,只需求针对改变的部分进行操作;
9.3、案例实操
场景:编写一个仿制文件的Task,并支撑增量构建。
class CopyTask extends DefaultTask {
// 指定输入
@InputFiles
FileCollection from
// 指定输出
@OutputDirectory
Directory to
// task action 履行
@TaskAction
def execute() {
File file = from.getSingleFile()
if (file.isDirectory()) {
from.getAsFileTree().each {
copyFileToDir(it, to)
}
} else {
copyFileToDir(from, to)
}
}
/**
* 仿制文件到文件夹
* @param src 要仿制的文件
* @param dir 接纳的文件夹
* @return
*/
private static def copyFileToDir(File src, Directory dir) {
File dest = new File("${dir.getAsFile().path}/${src.name}")
if (!dest.exists()) {
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(src).getBytes())
}
}
}
在编写Task的时分,咱们需求运用注解来声明输入和输出。@InputXXX
表明输入,@OutputXXX
表明输出。
- 上面代码中
from
便是咱们的输入,即要仿制的文件; -
to
是咱们的输出,即要接纳的文件夹; - 然后
execute()
办法便是Task履行的Action。
下面再来看看如何运用
tasks.register("CopyTask", CopyTask) {
from = files("from")
to = layout.projectDirectory.dir("to")
}
在履行前,造一下数据,在app目录新增一个from的文件夹,并在其下新增一个txt1.txt的文件
├── app
│ ├── from
│ │ └── txt1.txt
履行:
./gradlew CopyTask
txt1.txt文件现已仿制到to文件夹一份了。
此刻的目录结构:
├── app
│ ├── from
│ │ └── txt1.txt
│ └── to
│ └── txt1.txt
9.3.1、UP-TO-DATE
刚才履行的日志:
➜ GradleX git:(master) ✗ ./gradlew CopyTask
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
为了验证增量构建,再履行一次:
➜ GradleX git:(master) ✗ ./gradlew CopyTask
...
BUILD SUCCESSFUL in 1s
1 actionable task: 1 up-to-date
这次咱们能够发现,Task的履行成果现已由executed
变为up-to-date
了,阐明咱们的增量构建现已收效了。
尽管说此刻增量构建现已收效了,但完结度还不行,还需求有颗粒度更细的处理,接着看。
9.3.2、增量构建
上面咱们讲了增量构建的两种形式,也现已完成了up-to-date,现在要模仿输入/输出部分改变
的场景了。
场景:基于上面的场景,在from文件夹下添加一个txt2.txt文件,并支撑增量构建。
添加一个txt2.txt文件再次履行上面的指令时,会发现txt1.txt文件被再次仿制了一遍。
这是由于咱们的输入有了改变,CopyTask的Action就会全量构建,而咱们想要的作用是只仿制txt2.txt文件就好了。只对新增或修正的文件做仿制操作,没有改变的文件不进行仿制。
而要完成这种作用,就得让Action办法支撑增量构建
。
咱们需求给Action办法添加一个InputChanges
参数,带InputChanges类型参数的Action办法表明这是一个增量使命操作办法,该参数告知Gradle,该Action办法仅需求处理更改的输入,此外,Task还需求经过运用 @Incremental
或@SkipWhenEmpty
来指定至少一个增量文件输入特点。
class CopyTask extends DefaultTask {
// 指定增量输入特点
@Incremental
// 指定输入
@InputFiles
FileCollection from
// 指定输出
@OutputDirectory
Directory to
// task action 履行
@TaskAction
void execute(InputChanges inputChanges) {
boolean incremental = inputChanges.incremental
println("isIncremental = $incremental")
inputChanges.getFileChanges(from).each {
if (it.fileType != FileType.DIRECTORY) {
ChangeType changeType = it.changeType
String fileName = it.file.name
println("ChangeType = $changeType , ChangeFile = $fileName")
if (changeType != ChangeType.REMOVED) {
copyFileToDir(it.file, to)
}
}
}
}
/**
* 仿制文件到文件夹
* @param src 要仿制的文件
* @param dir 接纳的文件夹
* @return
*/
static def copyFileToDir(File file, Directory dir) {
File dest = new File("${dir.getAsFile().path}/${file.name}")
if (!dest.exists()) {
dest.createNewFile()
}
dest.withOutputStream {
it.write(new FileInputStream(file).getBytes())
}
}
}
这儿的改动分为两步:
- 给from特点添加
@Incremental
注解,表明增量输入特点; - 重写了action办法execute(),添加了
InputChanges
参数,支撑增量仿制文件,然后依据文件的ChangeType
做校验,只仿制新增或修正的文件。
ChangeType的几种类型:
public enum ChangeType {
ADDED,
MODIFIED,
REMOVED
}
- ADDED:表明文件是新增的;
- MODIFIED:表明文件是修正的;
- REMOVED:表明文件被删去;
咱们先来履行看看:
./gradlew CopyTask
输出:
> Task :app:CopyTask
isIncremental = false
ChangeType = ADDED , ChangeFile = txt1.txt
第一次履行,并没有增量构建,再履行一次看看。
BUILD SUCCESSFUL in 2s
1 actionable task: 1 up-to-date
第2次直接up-to-date了。
9.3.3、ADDED
这时咱们还没有验证到增量构建,咱们往from文件夹下添加一个txt2.txt文件,再履行看看
> Task :app:CopyTask
isIncremental = true
ChangeType = ADDED , ChangeFile = txt2.txt
经过日志能够看出,action的增量构建收效了,并表明txt2.txt是新增的,而txt1.txt文件没有再仿制一遍。
此刻的目录结构:
├── app
│ ├── build.gradle
│ ├── from
│ │ └── txt1.txt
│ └── txt2.txt
│ └── to
│ ├── txt1.txt
│ └── txt2.txt
9.3.4、MODIFIED
咱们还能够进一步验证,在txt1.txt文件里添加一行“yechaoa”模仿修正,再次履行
> Task :app:CopyTask
isIncremental = true
ChangeType = MODIFIED , ChangeFile = txt1.txt
依然是增量构建,并表明txt1.txt文件是修正的,而txt2.txt文件没有再仿制一遍。
9.3.5、REMOVED
在验证一下删去,咱们把from文件夹下的txt2.txt文件删去后履行看看
> Task :app:CopyTask
isIncremental = true
ChangeType = REMOVED , ChangeFile = txt2.txt
依然是增量构建,并表明txt2.txt文件被删去了。
此刻的目录结构:
├── app
│ ├── from
│ │ └── txt1.txt
│ └── to
│ ├── txt1.txt
│ └── txt2.txt
能够发现,咱们尽管把from文件夹下的txt2.txt文件删去了,Task的Action也确实支撑增量构建了,可是to文件夹下的txt2.txt文件还是在的,假如to文件夹的内容会影响到你的构建成果,还是要处理一下保持同步的。
9.4、增量vs全量
Task并不是每次履行都是增量构建,咱们能够经过InputChanges的isIncremental办法判别本次构建是否是增量构建,不过有以下几种状况会全量构建:
- 该Task是第一次履行;
- 该Task只需输入没有输出;
- 该Task的upToDateWhen条件返回了false;
- 自前次构建以来,该Task的某个输出文件已更改;
- 自前次构建以来,该Task的某个特点输入发生了改变,例如一些根本类型的特点;
- 自前次构建以来,该Task的某个非增量文件输入发生了改变,非增量文件输入是指没有运用@Incremental或@SkipWhenEmpty注解的文件输入.
当Task处于全量构建时,即InputChanges的isIncremental办法返回false时,经过InputChanges的getFileChanges办法能获取到一切的输入文件,而且每个文件的ChangeType都为ADDED,当Task处于增量构建时,即InputChanges的isIncremental办法返回true时,经过InputChanges的getFileChanges办法能获取到只发生改变的输入文件。
9.5、常用的注解类型
注解 | 类型 | 意义 |
---|---|---|
@Input | 任何Serializable类型或依靠性解析成果类型 | 一个简单的输入值或依靠联系解析成果 |
@InputFile | File* | 单个输入文件(不是目录) |
@InputDirectory | File* | 单个输入目录(不是文件) |
@InputFiles | Iterable* | 可迭代的输入文件和目录 |
@OutputFile | File* | 单个输出文件(不是目录) |
@OutputDirectory | File* | 单个输出目录(不是文件) |
@OutputFiles | Map<String, File>*或Iterable | 输出文件的可迭代或映射。运用文件树会封闭使命的缓存。 |
@OutputDirectories | Map<String, File>*或Iterable | 输出目录的可迭代。运用文件树会封闭使命的缓存。 |
@Nested | 任何自界说类型 | 自界说类型,或许无法完成Serializable,但至少有一个字段或特点标记了此表中的注释之一。它甚至或许是另一个@Nested。 |
@Internal | 任何类型 | 表明该特点在内部运用,但既不是输入也不是输出。 |
@SkipWhenEmpty | File或Iterable* | 与@InputFiles或@InputDirectory一同运用,告知Gradle在相应的文件或目录为空时越过使命,以及运用此注释声明的一切其他输入文件。由于声明此注释为空的一切输入文件而越过的使命将导致显着的“无源”成果。例如,NO-SOURCE将在控制台输出中发出。暗示@Incremental。 |
@Incremental | 任何类型 | 与@InputFiles或@InputDirectory一同运用,指示Gradle盯梢对带注释的文件特点的更改,因而能够经过@InputChanges.getFileChanges()查询更改。增量使命需求。 |
@Optional | 任何类型 | 与可选API文档中列出的任何特点类型注释一同运用。此注释禁用对相应特点的验证检查。有关更多详细信息,请参阅验证部分。 |
更多可检查文档。
9.6、增量构建原理
在初次履行Task之前,Gradle会获取输入的指纹,此指纹包括输入文件的途径和每个文件内容的散列。然后履行Task,假如Task成功完结,Gradle会获取输出的指纹,此指纹包括一组输出文件和每个文件内容的散列,Gradle会在下次履行Task时保存两个指纹。
后续每次在履行Task之前,Gradle都会对输入和输出进行新的指纹识别,假如新指纹与之前的指纹相同,Gradle假设输出是最新的,并越过Task,假如它们不相同,Gradle会履行Task。Gradle会在下次履行Task时保存两个指纹。
假如文件的核算信息(即lastModified和size)没有改动,Gradle将重复运用前次运转的文件指纹,即当文件的核算信息没有改变时,Gradle不会检测到更改。
Gradle还将Task的代码视为使命输入的一部分,当Task、Action或其依靠项在履行之间发生改变时,Gradle认为该Task是过期的。
Gradle了解文件特点(例如持有Java类途径的特点)是否对次序灵敏,当比较此类特点的指纹时,即使文件次序发生改变,也会导致Task过期。
请留意,假如Task指定了输出目录,则自前次履行以来添加到该目录的任何文件都会被疏忽,而且不会导致Task过期,如此不相关的Task或许会共享一个输出目录,而不会彼此干扰,假如出于某种原因这不是你想要的行为,请考虑运用TaskOutputs.upToDateWhen(groovy.lang.Closure)。
另请留意,更改不可用文件的可用性(例如,将损坏的符号链接的目标修正为有效文件,反之亦然),将经过最新检查进行检测和处理。
Task的输入还用于核算启用时用于加载Task输出的构建缓存密钥。
10、查找Task
有时分咱们需求找到官方的Task来hook操作,比方加个Action;有时分咱们也能够找到自界说的Task让它依靠某个官方Task。
查找Task,首要触及到TaskContainer目标,望文生义,Task容器的管理类,它供给了两个办法:
- findByPath(String path),参数可空
- getByPath(String path),参数可空,找不到Task会抛反常UnknownTaskException
同时,TaskContainer承继自TaskCollection
和NamedDomainObjectCollection
,又添加了两个办法能够运用:
- findByName
- getByName
参数界说与xxxByPath办法相同。
10.1、findByName
示例:
def aaa = tasks.findByName("yechaoa").doFirst {
println("yechaoa excuted doFirst by findByName")
}
找到一个名为「yechaoa」的Task,并添加一个doFirst Action,然后在doFirst中打印日志。
咱们这时分履行aaa是不会触发yechaoa Task Action的履行,由于并没有依靠联系,所以得履行yechaoa Task。
履行:
./gradlew yechaoa
输出:
> Task :app:yechaoa
yechaoa excuted doFirst by findByName
...
能够看到咱们加的日志现已打印出来了。
10.2、findByPath
示例:
def bbb = tasks.findByPath("yechaoa").doFirst {
println("yechaoa excuted doFirst by findByPath")
}
输出:
> Task :app:yechaoa
yechaoa excuted doFirst by findByPath
yechaoa excuted doFirst by findByName
...
10.3、named
经过称号查找Task,假如没有会抛反常UnknownTaskException
tasks.named("yechaoa") {
it.doFirst {
println("yechaoa excuted doFirst by named")
}
}
输出:
> Task :app:yechaoa
yechaoa excuted doFirst by named
yechaoa excuted doFirst by findByPath
yechaoa excuted doFirst by findByName
...
10.4、其他
withType:
tasks.withType(DefaultTask).configureEach(task -> {
if (task.name.toLowerCase().contains("copytask")) {
println(task.class.name)
}
})
each/forEach/configureEach:
tasks.each {
// do something
}
tasks.forEach(task->{
// do something
})
tasks.configureEach(task -> {
// do something
})
11、番外
11.1、register和create的差异
创立Task除了上面示例中的register
办法,还有create
办法,那它们有什么差异呢?
- 经过register创立时,只需在这个task被需求时,才会创立和装备;
- 经过create创立时,则会立即创立与装备该Task,并添加到TaskContainer中;
直白话便是,register是按需创立task的办法,这样gradle履行的性能更好(并不是你项目的性能)。
create创立task的办法官方现已不推荐了,尽管现在还没标@Deprecated,但未来也或许会被废弃掉。
可是需求留意的是,register归于懒加载,嵌套创立的Task在装备阶段无法被初始化,所以并不会被履行到。
除了register和create,还有一个replace办法,用于替换称号已存在的Task。
11.2、Task Tree
咱们能够经过./gradlew tasks
来检查一切的Task,但却看不到Task的依靠联系。
要检查Task的依靠联系,咱们能够运用task-tree插件
plugins {
id "com.dorongold.task-tree" version "2.1.1"
}
运用时咱们只需求在指令后边加上taskTree
就行了
gradle <task 1>...<task N> taskTree
示例:
./gradlew build taskTree
输出:
:app:build
+--- :app:assemble
| +--- :app:assembleDebug
| | +--- :app:mergeDebugNativeDebugMetadata
| | | --- :app:preDebugBuild
| | | --- :app:preBuild
| | --- :app:packageDebug
| | +--- :app:compileDebugJavaWithJavac
| | | +--- :app:compileDebugAidl
| | | | --- :app:preDebugBuild *
| | | +--- :app:compileDebugKotlin
......
12、最终
至此关于Gradle Task的部分就介绍完了。
从Task是什么、写在哪、怎样写、怎样运转、怎样写好等方面为切入点,由浅入深、依次递进的介绍了Task Action履行次序、自界说Task、Task依靠、Task增量构建等相关知识,总的来说,触及的知识点还是不少的,更需求在实践中去了解并应用。
期望本文对你有所协助~
13、Github
github.com/yechaoa/Gra…
14、相关文档
- Authoring Tasks
- TaskContainer
- DefaultTask
- Developing Custom Gradle Task Types
- Incremental build
- Build Script Basics
- Gradle的快速入门学习
- gradle-task-tree