破解Gradle(五) Task完全掌握

一个 Task 是 Gradle 里项目构建的原子执行单元,Gradle 通过将一个个Task串联起来完成具体的构建任务,每个 Task 都属于一个 Project。

Task API文档

开始前,我们执行./gradlew tasks来查看项目中有多少Task。

可以看到如下输出:

image-20220110100252043

上图就是当前工程中的每条task都已罗列出,并且有黄色的输出表示当前task的描述。

一、Task定义及配置

常见的有两种定义方式:

//直接通过task函数创建
task helloTask{
  println "i am hellloTask"
}

//通过TaskContationer去创建Task
this.tasks.creat(name: "helloTask2"){
  println "i am helloTasks2"
}

TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。

不管哪种创建Task的同时,我们还可以配置它的一些列属性,如:

//定义分组 添加注释
task helloTask(group: 'test', description: 'task study'){
  println 'i am hellpTask'
}

this.tasks.create(name: 'hellpTask2'){
  setGroup('imooc')
  setDescription('task study')
  println 'i am hellpTask2'
}

添加了group后,我们打开侧边Tasks目录下就会多出个test的分组,更方便查找。

目前 官方所支持的属性 可以总结为如下表格:

选型描述默认值
“name”task 名字无,必须指定
“type”需要创建的 task ClassDefaultTask
“action”当 task 执行的时候,需要执行的闭包 closure 或 行为 Actionnull
“overwrite”替换一个已存在的 taskfalse
“dependsOn”该 task 所依赖的 task 集合[]
“group”该 task 所属组null
“description”task 的描述信息null
“constructorArgs”传递到 task Class 构造器中的参数null

在开发中,如何引用另一个task的属性?如下所示:

//使用 defaultTasks 关键字 来将一些任务标识为默认的执行任务
defaultTasks "Gradle_First", "Gradle_Last"

task Gradle_First() {
  //使用 ext 给 task 自定义需要的属性
    ext.good = true
}

task Gradle_Last() {
    doFirst {
        println Gradle_First.goodg
    }
    doLast {
      //使用 "$" 来引用另一个 task 的属性
        println "I am not $Gradle_First.name"
    }
}

二、 Task执行详解

在gradle脚本的配置阶段都会执行,也就是说不管执行脚本里的哪个任务,所有的task里的配置代码都会被执行,但是我们期望的是调用一个方法的时候,这个方法里的代码才会执行。这里就要涉及到Task Action的概念了。

Task通常用doFirst和dolast两个方式用于在执行期间进行操作:

task helloTask(group: 'test', description: 'task study'){
  println 'i am hellpTask'
  //表示 task 执行最开始的时候被调用的 Action。
  doFirst{
    println 'the task group is:' + group
  }
  
  //表示 task 将执行完的时候被调用的 Action。
  doLast{
    println 'the task doLast'
  }
}
helloTask.doFirst{
   println 'the task description is:' + description
}

当我们执行./gradlew helloTask可以看到输出结果:

the task description is:task study
the task group is:test
the task doLast

从这里可以知道了单独调用方法会先执行,然后配置块中的再执行。

接下来,就用一个实战更加了解doFirstdoLast的用法,来实现计算build执行期间的耗时:

//计算build执行时长
def startBuildTime, endBuildTime

 //保证要找的task已经配置完毕
this.afterEvaluate { Project project ->
   // 执行build任务时,第一个被执行的Task
        def preBuildTask = project.tasks.getByName('preBuild')
        preBuildTask.doFirst {
          //获取第一个 task 开始执行时刻的时间戳
            startBuildTime = System.currentTimeMillis()
        }

	//找到当前 project 下最后一个执行的 task,即 build task
        def buildTask = project.tasks .getByName('build')
        buildTask.doLast {
	    //获取最后一个 task 执行完成前一瞬间的时间戳
            endBuildTime = System.currentTimeMillis()
            println "Current project execute time is ${endBuildTime - startBuildTime}"
        }
}

三、 Task的依赖和执行顺序

在task执行顺序方式,有三种方式可以规定task的执行顺序:

3.1 dependsOn强依赖方式

通过下面例子来了解Task如何进行依赖:

task taskX{
    doLast{
        println 'taskX'
    }
}

task taskY{
    doLast{
        println 'taskY'
    }
}

//通过定义dependsOn依赖了taskX 和tasky
task taskZ(dependsOn: [taskX, taskY]){
  doLast{
    print 'taskZ'
  }
}
//另一种方式
taskZ.dependsOn(taskX, taskY)

再执行后就可以看到先执行了taskX和taskY,在执行taskZ。

> Task :app:taskX
taskX

> Task :app:taskY
taskY

> Task :app:taskZ
taskZ

如果定义一个task不知道依赖具体的task的时候该怎么办?这时就要我们动态的进行依赖。

task lib1 {
    doLast{
        println 'lib1'
    }
}

task taskZ(dependsOn: [taskX, taskY]){
    //依赖所有以lib开头的任务
    dependsOn this.tasks.findAll{task ->
        return task.name.startsWith('lib')
    }
    doLast{
        println 'taskY'
    }
}

下面还是用一个例子来介绍下task依赖在实战中的使用,来解析一个xml文件并进行输出

//解析xml文件
task handleReleaseFile{
    def srcFile = file('releases.xml')
    def destDir = new File(this.buildDir, 'generated/release/')
    doLast{
        println '开始解析对应的xml文件'
        destDir.mkdir()
        def releases = new XmlParser().parse(srcFile)
        releases.release.each{ releaseNode ->
            //解析每个reslease结点的内容
            def name = releaseNode.versionName.text()
            def versionCode = releaseNode.versiointCode.text()
            def versionInfo = releaseNode.versionInfo.text()
            //创建文件并写入结点数据
            def destFile = new File(destDir, "release-${name}.text")
            destFile.withWriter{ writer ->
                writer.write("${name} -> ${versionCode} -> ${versionInfo}")
            }
        }
    }
}

task handleReleaseFileTest(dependsOn: handleReleaseFile){
    //得到该目录
    def dir = fileTree(this.buildDir.path + 'generated/release/')
    doLast {
        dir.each {
            println 'the file name is:' + it
        }
        println '输出完成....'
    }
}

然后执行 ./gradlew handleReleaseFileTest命令,可以得到如下输出:

> Task :app:handleReleaseFile
开始解析对应的xml文件

> Task :app:handleReleaseFileTest
输出完成....

在我们执行测试任务的时候,也会执行相对应的依赖任务。之后在自定义的输出目录下面就会有相应的文件生成:

image-20220110234942034

上面提到的release.xml文件如下:

<release>
  <release>
    <versionCode>100</versionCode>
    <versionName>1.0.0</versionName>
    <versionInfo>App的第一个版本, 上线了一些基础功能</versionInfo>
  </release>
</release>

3.2 通过Task输入输出指定

也可以通过Task来指定输入输出,那我们就使用这种方式来完成一个自动维护版本发布文档的gradle脚本。

ext{
    versionName = '1.0.0'
    versionCode = '100'
    versionInfo = 'App的第一个版本, 上线了一些基础功能'
    destFile = file('release.xml')
    if (destFile != null && !destFile.exists()){
        destFile.createNewFile()
    }
}

task writeTask{
    //为task指定输入
    inputs.property('versionCode', this.versionCode)
    inputs.property('versionName', this.versionName)
    inputs.property('versionInfo', this.versionInfo)
    //为Task指定输出
    outputs.file this.destFile

    doLast {
        //将输入的内容写进去
        def data = inputs.getProperties()
        File file = outputs.getFiles().getSingleFile()
        //将map转化为实体对象
      def versionMsg = new VersionMsg(data)
      
      //将实体对象写入到xml中
   		....... 
   }
  
  task readTask{
    inputs.file destFile
    doLast {
      	//读取输入文件的内容并显示
        def file = inputs.files.singleFile
        println file.text
    }
}
  
  //最后对它进行测试
  task taskTest{
    dependsOn readTask, writeTask
    doLast {
        println '输入输出任务结束'
    }
}

在这里我们定义了readTask和writeTask,一个负责读取另一个则负责写入。在writeTask任务中指定了输出文件destFile,并把版本信息写入进去。最后在TaskTest任务重使用的dependdOn将两个task关联起来,这样就完成了这个例子–releaseinfo.gradle

可是每一次执行都要我们手动执行,有没有可以直接在构建中就帮我们执行了这个任务?

//挂载到build构建中去
project.afterEvaluate {project ->
    def buildTask = project.tasks.getByName('build')
    if (buildTask == null){
        throw GradleException('the build task is not found')
    }

    //buildTask.finalizedBy "writeTask"
    buildTask.doLast{
        writeTask.finalizedBy()
    }
}

3.3 通过API指定执行顺序

我们还可以在闭包中通过mustRunAfter方法指定task的依赖顺序,它必须结合dependesOn强依赖进行配套使用。

task taskX {
  //如果同时执行taskX、testY这2个task,会确保testY执行完之后才执行taskX这个task,用这个来保证执行顺序
    mustRunAfter "taskY"

    doFirst {
        println "this is taskX"
    }
}

task taskY {
    // 使用 mustRunAfter 指定依赖的(一至多个)前置 task
    // 也可以使用 shouldRunAfter 的方式,但是是非强制的依赖
		// shouldRunAfter taskA
    doFirst {
        println "this is taskY"
    }
}

task taskZ(dependsOn: [taskX, taskY]) {
    mustRunAfter "taskY"
    doFirst {
        println "this is taskZ"
    }
}

四、Task类型

Task已经帮我们定义了很多Task Type供我们使用,平时用的时候多查看下官方文档Gradle DSL API文档中的Task types那一栏下。

Gradle本身还提供了一些已有的Task供我们使用,比如Copy、Delete等。因此我们定义Task的时候是可以继承已有的Task,如下实例代码:

// 将 doc 复制到 build/target 目录下
task copyDocs(type: Copy) {
    from 'src/main/doc'
    into 'build/target/doc'
}

// 删除根目录下的 build 文件
task clean(type: Delete) {
    delete rootProject.buildDir
}

另外我们也可以自定义task来引用:

class SayHelloTask extends DefaultTask {
    
    String msg = "default name";
    int age = 20

  //@TaskAction 表示该Task要执行的动作,即在调用该Task时,sayhello()方法将被执行
    @TaskAction
    void sayHello() {
        println "Hello $msg ! Age is ${age}"
    }
}

// hello使用了默认的message值
task hello(type:SayHelloTask)

// 重新设置了message的值
task hello1(type:SayHelloTask){
    message ="I am an android developer"
}

五、挂接自定Task到构建过程

如果想把自定义Task挂接到生命周期中间部分,而不是开头或者结束位置,又是如何实现?

学习这个最好是查看Tinker源码,在里面可以找到定义了如何挂接自定义Task到构建过程中:

image-20220111160233216

可以转化为构建图理解起来更清晰,这样自定义的task就放在了processReleaseManifest之后processReleaseResources之前了。

image-20220111160648793

六、TaskContainer接口解析

TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。

//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

//创建task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task

//当task被加入到TaskContainer时的监听
whenTaskAdded(action: Closure)

可以看下具体的使用例子:

//当有task创建时
getTasks().whenTaskAdded { Task task ->
    println "The task ${task.getName()} is added to the TaskContainer"
}

//采用create(name: String)创建
getTasks().create("task1")

//采用create(options: Map<String, ?>)创建
getTasks().create([name: "task2", group: "MyGroup", description: "这是task2描述", dependsOn: ["task1"]])

//采用create(options: Map<String, ?>, configure: Closure)创建
getTasks().create("task3", {
    group "MyGroup"
    setDependsOn(["task1", "task2"])
    setDescription "这是task3描述"
})

//通过名字查找指定的task
def task3 = getTasks().findByName("task3")
println "findByName() return task is " + task3

def taskList = getTasks().withType(DefaultTask)
def count = 0
//遍历所有的task,打印出其名字
taskList.all { Task t ->
    println "${count++} task name is ${t.name}"
}

今天就到这里了,接下去的Gradle开发都离不开task和gradle这两块核心内容,还是要好好掌握住的。

参考

深入理解Android之Gradle

深度探索 Gradle 自动化构建技术(三、Gradle 核心解密)
Project文档

Android Gradle学习(三):Task进阶学习

Task文档

TaskContainer API

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值