Gradle高阶-Task执行

8,715 阅读5分钟

回顾

还记得上节内容中的例子吗?

task helloTask(group:'personal',description:'task learn'){
    println 'this is hello task.'
}
this.tasks.create(name:'task1'){
    setGroup('personal')
    setDescription('task learn')
    println 'this is hello task1.'
}

不知道你在执行 ./gradlew helloTask 的命令的时候有没有注意到,在控制台中输入了如下语句:

this is hello task.
this is hello task1.
Q:很明显在控制台中将task1的任务也输出了,这是为什么呢?    

A:其实很简单,因为这两个输出语句都是在task的配置阶段去打印的,而在project中任何的task都是会执行的,所以这两个task就都会打印出来。

执行阶段

有时候我们并不需要在配置阶段就执行我们加载好的代码,而是在执行阶段才需要执行该怎么办呢?

我们可以为task在执行阶段中指定要执行的代码,而且也只有task能在执行阶段可以执行,task是gradle中的重要的核心部分,它占据gradle的一个生命周期。

如果我们想要在执行阶段来执行task,那个就需要借助task中的

doFirst()
doLast()

这两个方法,这两个使用方式相同,并且可以有多个存在,执行顺序将会按照我们编写的代码顺序执行

ttask helloTask(group:'personal',description:'task learn'){
    println 'this is hello task.'
    doFirst{
      println 'this task group is:'+group  
    }
    doFirst{
      println 'this is doFirst2'  
    }
   
}

也可以在外部调用

helloTask.doFirst{
     println 'this is doFirst3'
}

这两者加载输出有什么区别呢?
执行

./gradlew helloTask

输出

>>> helloTask
>>> this is doFirst3
>>> println 'this task group is:personal  

从输出的语句中我们看到:

1. 首先输出了这个task的名称
2. 然后输出了我们在外部调用的doFirst()方法
3. 最后才输出了闭包中的doFirst()方法

也就是说如果我们想要task在执行阶段执行的话,必须要添加doFirst或者是doLast的闭包代码

那么我们就使用doFirst和doLast来实现一个功能吧

案例:计算build执行时长的gradle代码

def startTime,endTime

//监听执行阶段的完成(该回调可以确保所有的task都已经配置完成)
this.afterEvaluate{Project project->
    //寻找最开始的task
    def preBuildTask=project.tasks.getByName('preBuild')
    preBuildTask.doFirst{
        startTime=System.currentTimeMills()
        println 'the start time is :'+startTime
    }
    //寻找build task
     def buildTask=project.tasks.getByName('build')
     preBuildTask.doLast{
        endTime=System.currentTimeMills()
        println 'the endTime time is :${endTime-startTime}'
    }
}

或许有人会有疑惑,你怎么知道哪个task先执行?哪个最后执行呢?

这个就需要我们平时观察控制台输出中的内容了,在我们执行完 ./gradlew build操作后,控制台就会输出一大堆的内容,我们就能看到输出如下信息

app:preBuild
    ...
    ...
    ...
app:build

而这两个就是我们需要的task名称,通过观察控制台就能得到我们想要的信息

执行顺序

Taks的执行顺序由下面的三种方式来决定的

  • dependsOn强依赖方式
task taskX{
    doLast{
        println 'taskX'
    }
}
task taskY{
    doLast{
        println 'taskY'
    }
}
task taskZ{
    doLast{
        println 'taskZ'
    }
}

如果我们要为taskZ添加已知依赖,那么我们就需要使用dependsOn

//单个
task taskZ(dependsOn:taskX){
    doLast{
        println 'taskZ'
    }
}
//多个
task taskZ(dependsOn:[taskX,taskY]){
    doLast{
        println 'taskZ'
    }
}
等价于
taskZ.dependsOn(taskX,taskY)

在为Task添加依赖了后,那个我们输出的不仅仅是taskZ的内容了

我们可以通过将dot转换成下图,显示为:

如果想动态添加依赖,就需要dependsOn闭包方式添加依赖

// << 等价于doLast
task lib1 << {

        println 'lib1' 
}
task lib2 << {
        println 'lib2'
}
task noLib << {
        println 'noLib'
}


task taskZ{
dependsOn this.task.findAll{
    //依赖以lib开头的依赖
    task->return task.name.startsWith('lib')
}
    doLast{
        println 'taskZ'
    }
}

案例:版本升级管理

versionfile.xml

<?xml version="1.0" encoding="utf-8">
<releases>
    <release>
        <versionCode>100<versionCode>
        <versionName>1.0.0<versionName>
        <versionInfo>新功能上线了<versionInfo>
    <release>
    <release>
        <versionCode>101<versionCode>
        <versionName>1.0.1<versionName>
        <versionInfo>新功能上线了111<versionInfo>
    <release>
<releases>
</xml>

gradle脚本实现版本升级管理,首先我们解析xml,然后拿到每个节点的信息

task versionControlFile {
    def srcFile=file("versionfile.xml")
    def destDir=new File(this.buildDir,'generated/release/')
    doLast{
        println '开始解析versionfile.xml文件'
        destDir.mkdir()
        //解析xml文件 根节点releases
        def releases=new XmlParse().parse(srcFile)
        //解析release并且遍历
        releases.release.each{releaseNode->
            def code=releaseNode.versionCode.text()
            def name=releaseNode.versionName.text()
            def info=releaseNode.versionInfo.text()
             //创建文件并写入节点数据
             def destFile=new File(destDir,'release-${name}.text')
             destFile.withWrite{
                 writer->writer.wirte("${name}-> ${code} -> ${info}")
             }
        }
       
        
    }
}

下面我们可以通过编写一个测试代码测试脚本是否能正确执行

task versionTest(dependsOn:versionControlFile){
    def dir=fileTree(this.buildDir.path+'generated/release/')
    doLast{
        dir.each{
            println 'the file name is '+it
        }
    }
    println '完成输出......'
}
  • 通过task指定输入输出方式

首先了解常见的方法

TaskInputs.java

TaskInputs files(Objects... path)

TaskInputs file(Object path)

TaskInputs dir(Object dirPath)

TaskInputs property(String name,Object value)

TaskInptus property(Map<String,?> properties)

TaskOutputs.java

TaskOutputs files(Objects... path)

TaskOutputs file(Object path)

TaskOutputs dir(Object dirPath)

使用指定输入输出方式改造一下gradle脚本实现版本升级管理的代码

ext{
    versionName='1.0.0'
    versionCode='100'
    versionInfo='新版本上线'
    destFile=file('versioncotrol.xml')
    if(destFile!=null&&destFile.exists()){
        destFile.createNewFile()
    }
}

task wirteTask{
    //指定输入
    inputs.property('versionCode',this.versionCode)
    inputs.property('versionName',this.versionName)
    inputs.property('versionInfo',this.versionInfo)
    //指定输出
    outputs.file destFile
    
    doLast{
        //获取数据
        def data=inputs.getProperties()
        File outfile=outputs.getFiles().getSingleFile()
        //将map类型的data数据转换成实体对象
        def versionMsg=new VersionMsg(data)
        //将实体对象转换成xml格式
        def sw=new StringWriter()
        def xmlBuild=new MakeUpBuilder(sw)
        if(outfile.text!=null && outfile.text.size<=0){
            //文件中没有内容
            xmlBuild.releases{
                release{
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
            }
            outfile.withWriter{
                writer->writer.append(sw.toString())
            }
        }else{
            //已有版本信息
             xmlBuild.release{
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
            }
            //将生成的数据插入到根节点之前
            def lines=outfile.readLines()
            def lengths=lines.size()-1
             outfile.withWriter{
                writer->
               lines.eachWithIndex(String line,int index->
                if(index!=lengths){
                   writer.append(line+"\r\n") 
                }else{
                     writer.append("\r\n"+sw.toString()+"\r\n")
                     writer.append(lines.get(lengths))
                }
               )
            }
        }
    }
}

task readTask{
    inputs.file destFile
    doLast{
        def file=inputs.files.getSingleFile
        println file.text
    }
}

下面我们可以通过编写一个测试代码测试脚本是否能正确执行

task verTest{
    dependsOn writeTask,readTask
    doLast{
         println '完成输出......'
    }
   
}

注意:dependsOn强依赖方式和通过task指定输入输出方式是等效的

  • 通过API指定执行顺序
task taskX{
    doLast{
        println 'taskX'
    }
}
task taskY{
    doLast{
        println 'taskY'
    }
}
task taskZ{
    doLast{
        println 'taskZ'
    }
}

为它们三个指定执行逻辑,X->Y-Z,那么我们可以使用

  • mustRunAfter
task taskX{
    doLast{
        println 'taskX'
    }
}
task taskZ{
mustRunAfter taskY
    doLast{
        println 'taskZ'
    }
}
task taskY{
mustRunAfter taskX
    doLast{
        println 'taskY'
    }
}

  • shouldRunAfter
task taskX{
    doLast{
        println 'taskX'
    }
}
task taskZ{
shouldRunAfter taskY
    doLast{
        println 'taskZ'
    }
}
task taskY{
shouldRunAfter taskX
    doLast{
        println 'taskY'
    }
}

mustRunAftershouldRunAfter作用是一样的,但是must具有强制性

挂接task到构建过程中

首先我们将之前的代码抽离成一个独立gradle文件
versioncotrol.gradle

import groovy.xml.MakeUpBuilder
ext{
    versionName='1.0.0'
    versionCode='100'
    versionInfo='新版本上线'
    destFile=file('versioncotrol.xml')
    if(destFile!=null&&destFile.exists()){
        destFile.createNewFile()
    }
}

task wirteTask{
    //指定输入
    inputs.property('versionCode',this.versionCode)
    inputs.property('versionName',this.versionName)
    inputs.property('versionInfo',this.versionInfo)
    //指定输出
    outputs.file destFile
    
    doLast{
        //获取数据
        def data=inputs.getProperties()
        File outfile=outputs.getFiles().getSingleFile()
        //将map类型的data数据转换成实体对象
        def versionMsg=new VersionMsg(data)
        //将实体对象转换成xml格式
        def sw=new StringWriter()
        def xmlBuild=new MakeUpBuilder(sw)
        if(outfile.text!=null && outfile.text.size<=0){
            //文件中没有内容
            xmlBuild.releases{
                release{
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
            }
            outfile.withWriter{
                writer->writer.append(sw.toString())
            }
        }else{
            //已有版本信息
             xmlBuild.release{
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
            }
            //将生成的数据插入到根节点之前
            def lines=outfile.readLines()
            def lengths=lines.size()-1
             outfile.withWriter{
                writer->
               lines.eachWithIndex(String line,int index->
                if(index!=lengths){
                   writer.append(line+"\r\n") 
                }else{
                     writer.append("\r\n"+sw.toString()+"\r\n")
                     writer.append(lines.get(lengths))
                }
               )
            }
        }
    }
}

task readTask{
    inputs.file destFile
    doLast{
        def file=inputs.files.getSingleFile
        println file.text
    }
}

//开始挂接
this.project.afterEvaluate{project->
    def buildTask=project.tasks.getByName('build')
    if(buildTask==null) {
        throws GradleException('the build task is not found')
    }
    buildTask.doLast{
        //在build任务执行后将执行wirteTask任务
        wirteTask.execute()
    }
    
}

然后在app.gradle引入这个被抽离的gradle文件即可

学习Gradle挂载更多内容,请阅读Tinker源码

总结

  1. task可以在执行阶段执行,但是一定需要添加doFirst或者doLast方法
  2. task执行顺序
  3. 通过tinker源码了解task挂接