Task详解

简介: Task详解

1,Task 的创建


//直接通过 task 函数创建
task("helloTask") {
    println '--------- i am helloTask'
}
//通过 TaskContainer 去创建 Task
this.tasks.create(name: "helloTask2") {
    println '--------- i am helloTask2'
}


以上两种方式创建的 Task 没有任何区别,


this.tasks 返回的就是 TaskContainer 类,Task 的容器类,上面两种方式创建的 Task 都会被添加当前 project 的 TaskContainer 类中,TaskContainer 是用来管理当前 project 中所有的 Task。既然是用来管理的,他肯定可以对 Taks 进行一些操作,如下:

0a2653c851af460fa595bd959398a8f1.png

public interface TaskContainer extends TaskCollection<Task>, PolymorphicDomainObjectContainer<Task> {
  //在指定路径下查找 task ,没有返回 null
  @Nullable
    Task findByPath(String path);
    //同上,没有抛出异常
    Task getByPath(String path) throws UnknownTaskException;
    //非常多的创建方法
    Task create(Map<String, ?> options) throws InvalidUserDataException;
    Task create(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
    Task create(String name, Closure configureClosure) throws InvalidUserDataException;
    Task create(String name) throws InvalidUserDataException;
}


无论使用那种方式创建 Task ,最终 TaskContainer 会将所有的 Task 在配置阶段构成一个有向无环图。通过这个图 gradle 就能知道 task 的类别和执行顺序。


2,Task配置


可以在创建的时候直接配置组合描述信息,如下:


task helloTask(group: '345', description: 'Task Study') {
    println '--------- i am helloTask'
}


也可以在闭包中进行设置描述信息:


this.tasks.create(name: "helloTask2") {
    setGroup("345")
    setDescription("Task Study")
    println '--------- i am helloTask2'
}


第一种看起来比较简洁,指定 group 后 ,就会将 task 放在对应的组中。description 就相当于是注释。指定分组后,点击 as 右上角的 Gradle ,打开指定 task 的 module。就会显示当前 project 的所有 task。如果指定了分组,则就会有对应的文件夹,如果没有就会放在 other 中,如下:


0a2653c851af460fa595bd959398a8f1.png

当然 task 不会只能配置上面两种信息。打开 Task 类,如下:


public interface Task extends Comparable<Task>, ExtensionAware {
    //名字
    String TASK_NAME = "name";
  //描述
    String TASK_DESCRIPTION = "description";
  //组
    String TASK_GROUP = "group";
  //类型
    String TASK_TYPE = "type";
  //指定当前 task 依赖于那些 task
    String TASK_DEPENDS_ON = "dependsOn";
  //重写 task
    String TASK_OVERWRITE = "overwrite";
  //配置要执行的逻辑
    String TASK_ACTION = "action";
}


3,让 Task 执行在 执行阶段


首先我们有运行上面的例子 gradlew helloTask ,如下:


--------- i am helloTask
--------- i am helloTask2


这两个都执行了。为啥呢?他们是在 配置阶段执行的,并不是执行阶段,所以他们都会被执行。下面我们就看一下执行阶段。


执行阶段是 gradle 生命周期的第三阶段。也只有 Task 才能在第三阶段中执行,要将 Task 执行在 执行阶段,有两个方法,分别是 doFirst 和 doLast ,通过 doFirst 可以为已经有的 task 之前添加相应的逻辑,doLast 则是之后了,下面看一下简单的使用:


task helloTask(group: '345', description: 'Task Study') {
    // doFirst 执行在 执行阶段
    doFirst {
        println '--------- i am helloTask --1'
    }
    //可执行多次
    doFirst {
        println '--------- i am helloTask --2'
    }
    doLast{
        println "last"
    }
}
helloTask.doFirst {
    //可定义在外面
    println '--------- i am helloTask --3'
}


打印结果:


> Task :app:helloTask
--------- i am helloTask --3
--------- i am helloTask --2
--------- i am helloTask --1
last


在配置阶段执行的时候是不会打印 Task :app:helloTask 这句话的。这里的执行顺序是从外部的开始执行,最后执行的是 doLast 。


小例子:计算 Build 中 task 的执行时长


// 计算 build 计算时长
def startBuildTime, endBuildTime
this.afterEvaluate {
    Project p ->
        def preBuildTask = p.tasks.getByName('preBuild')
        preBuildTask.doFirst {
            startBuildTime = System.currentTimeMillis()
            println "this start time is :" + startBuildTime
        }
        def buildTask = p.tasks.getByName('build')
        buildTask.doLast {
            endBuildTime = System.currentTimeMillis()
            println "this build time is :${endBuildTime - startBuildTime}"
        }
}


使用命令 gradlew build 开始 build


执行结果:


> Task :app:preBuild
this start time is :1576560417687
> Task :app:build
this build time is :28783


其中 this.afterEvaluate 是生命周期中的方法,绘制配置阶段执行所有的 task 配置完成后执行,接着就是获取 preBuild task,他是第一个开始执行的 task,不信你可以看一下控制台的log。在 preBuild 的 doFirst 中获取开始时间,在 build task 完成后获取结束时间即可。


4,Task 执行顺序


看上面的小例子:我们执行的命令是 gradlew build ,那为啥先执行 preBuild 呢!这就和 Task 的执行顺序有关了。


2d65d23f6d4748949b924e4057485923.png


强依赖方式


为 task 指定多个依赖性的 task。这样当前的 task 必须等依赖的 task 执行完才可以执行


task TaskX {
    doLast {
        println 'taskX'
    }
}
task TaskY {
    doLast {
        println 'taskY'
    }
}
//依赖 taskX ,taskY
task TaskZ(dependsOn: [TaskX, TaskY]) {
    doLast {
        println 'taskZ'
    }
}
// TaskZ.denpendsOn(TaskX, TaskY) 这种也可以


执行结果:


> Task :app:TaskX

taskX


> Task :app:TaskY

taskY


> Task :app:TaskZ

taskZ


如果在最开始不知道要依赖那些 task,可以通过下面这种方式:


task TaskX {
    doLast {
        println 'taskX'
    }
}
task TaskY {
    doLast {
        println 'taskY'
    }
}
//依赖 taskX ,taskY
task Test {
    dependsOn this.tasks.findAll {
        task ->
            //测试此字符串是否以指定的前缀开始。
            return task.name.startsWith('Task')
    }
    doLast {
        println 'Test'
    }
}


结果如下:


> Task :app:TaskX

taskX


> Task :app:TaskY

taskY


> Task :app:Test

Test


通过 api 来指定顺序


//执行顺序指定
task taskX {
    doLast {
        println 'taskX'
    }
}
task taskY {
    //指定执行在 TaskX 之后
    mustRunAfter taskX
    doLast {
        println 'taskY'
    }
}
task taskZ {
    //指定执行在 TaskY 之后
    mustRunAfter taskY
    doLast {
        println 'taskZ'
    }
}


输入命令:gradlew taskX taskZ TASKZ 结果如下:


> Task :app:taskX

taskX


> Task :app:taskY

taskY


> Task :app:taskZ

taskZ


最后看一个例子:


ext {
    versionName = '1.0.1'
    versionCode = 1
    versionInfo = '第1个版本'
    destFile = file('releases.json')
    if (destFile != null && !destFile.exists()) {
        destFile.createNewFile()
    }
}

上面的 ext 不会陌生吧,他是扩展属性。内部定义了一写属性,创建了一个文件 releases.json


接着定义一个类,来保存这些属性:


class VersionMsg {
    String versionName
    String versionCode
    String versionInfo
}


然后创建一个 Task。用来给文件中写入信息:


task writeTask {
    //为 task 指定输入
    inputs.property('versionName', this.versionName)
    inputs.property('versionCode', this.versionCode)
    inputs.property('versionInfo', this.versionInfo)
    //为 task 指定输出
    outputs.file this.destFile
    doLast {
        //获取输入让的信息,返回 map
        def map = inputs.getProperties()
        File file = outputs.getFiles().getSingleFile()
        def versionMsg = new VersionMsg(map)
        def json = JsonOutput.toJson(versionMsg)
        if (file.text != null && file.text.size() >= 0) {
            def lines = file.readLines()
            def lengths = lines.size() //一共多少行
            file.withWriter {
                writer ->
                    if (lengths == 0) {
                        writer.append("[")
                        //将json串格式化后写入
                        writer.append("${JsonOutput.prettyPrint(json)}\n")
                    } else {
                        for (int i = 0; i < lines.size(); i++) {
                            if (i == lengths - 1) {
                                writer.append(",")
                                break
                            }
                            writer.append(lines[i] + "\n")
                        }
                        writer.append("${JsonOutput.prettyPrint(json)}\n")
                    }
                    writer.append("]")
            }
        }
    }
}


inputs 和 outputs 是 Task 的属性,inputs 可以是任意类型的对象,而 outputs只能是文件(或文件夹)


上面这段代码首先是指定了输入和输出,然后获取输入的文件,并传入到 VersionMsg类中,接着将这个类转为 json 串。最后将 json 写入到文件中。


注意要使用 JsonOutput ,必须先导包,否则不能使用,如下:


import groovy.json.JsonOutput


最终执行 gradlew writeTask 命令,执行前请修改信息,写入的文件如下:


0a2653c851af460fa595bd959398a8f1.png


这就是输出的版本信息,其中 contentHash 和 originalClassName 是自动生成的。


在这个过程中也遇到了一个问题:在执行 Task 的时候,如果 Task 内涉及的内容没有发生任何变化,那么这个 task 不会执行。如果上面的执行完第三次后没有修改任何地方,在继续执行 Task 则不会执行。但是只要有任何修改,task 都会执行:如将 文件中的 json 传格式化一下,修改一下等,再次执行命令,则 task 就会执行。


到这里就完了,但是还是有些疑惑,在学习的过程中,看到可以使用 输入输出来指定执行的顺序,但是我却没弄出来。还有就是指定在 build Task 后执行,但是定义的 task 没有 execute() 方法,一直报错,无法在代码中使其运行。


这就是输出的版本信息,其中 contentHash 和 originalClassName 是自动生成的。


在这个过程中也遇到了一个问题:在执行 Task 的时候,如果 Task 内涉及的内容没有发生任何变化,那么这个 task 不会执行。如果上面的执行完第三次后没有修改任何地方,在继续执行 Task 则不会执行。但是只要有任何修改,task 都会执行:如将 文件中的 json 传格式化一下,修改一下等,再次执行命令,则 task 就会执行。


到这里就完了,但是还是有些疑惑,在学习的过程中,看到可以使用 输入输出来指定执行的顺序,但是我却没弄出来。还有就是指定在 build Task 后执行,但是定义的 task 没有 execute() 方法,一直报错,无法在代码中使其运行。


希望有解决过的同学讲一哈,多谢


相关文章
|
28天前
|
前端开发 Java API
解密 asyncio 的 Future 和 Task
解密 asyncio 的 Future 和 Task
40 2
|
23天前
|
Java 数据库
详解Task 和 ValueTask 的使用区别
详解Task 和 ValueTask 的使用区别
25 0
Cannot add task ‘wrapper‘ as a task with that name already exists.
Cannot add task ‘wrapper‘ as a task with that name already exists.
189 0
c#编程:Task不包含Task.Run
c#编程:Task不包含Task.Run
317 0
|
Ubuntu Shell PyTorch
socker task2
天池龙珠docker训练营
148 0
|
Python
task01学习笔记
内容很详尽
262 0
|
Java C# vr&ar
C# Task用法
原文:C# Task用法 1、Task的优势   ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:   ◆ ThreadPool不支持线程的取消、完成、失败通知等交互性操作;   ◆ ThreadPool不支持线程执行的先后次序;   以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。
1644 0
|
数据采集 测试技术 Windows