我正在参加「启航方案」

1、前言

本文是Gradle系列的第7篇,给咱们带来Gradle构建中心Task相关的知识点。

【Gradle-7】Gradle构建核心之Task指南

2、Gradle中的Task是什么

Task是一个使命,是Gradle中最小的构建单元

Gradle构建的中心便是由Task组成的有向无环图:

【Gradle-7】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的几种办法:

  1. register(String name, Action<? super Task> configurationAction)
  2. register(String name, Class type, Action<? super T> configurationAction)
  3. register(String name, Class type)
  4. register(String name, Class type, Object… constructorArgs)
  5. 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的输出没有改动。

分几种状况:

  1. 输入和输出都没有改动;
  2. 输出没有改动;
  3. Task没有操作,有依靠,但依靠的内容是最新的,或许越过了,或许复用了;
  4. 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的履行次序也是多数人的一个误区,能够经过上面的日志,咱们梳理一下:

【Gradle-7】Gradle构建核心之Task指南

  • 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,双击即可履行。

【Gradle-7】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的输出。

【Gradle-7】Gradle构建核心之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文件夹一份了。

【Gradle-7】Gradle构建核心之Task指南

此刻的目录结构:

├── 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())
        }
    }
}

这儿的改动分为两步:

  1. 给from特点添加@Incremental注解,表明增量输入特点;
  2. 重写了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承继自TaskCollectionNamedDomainObjectCollection,又添加了两个办法能够运用:

  • 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