一、学习目标

  • Task相关
  • Task定义及配置
  • Task的执行顺序
  • Task类型
  • Task挂接到构建生命周期
  • Task实战

二、Task实战和详解

Task定义

查看项目中有多少 Task 

./gradlew tasks

创建Task

创建方法一:利用Task函数来创建

//config.gradle
task helloTask {
    println 'I am helloTask'
}

task('taskOne'){
    println 'taskOne'
}

//执行 helloTask
./gradlew helloTask

//执行 taskOne
./gradlew taskOne

创建方法二:利用TaskContainer来创建Task

//config.gradle
this.tasks.create(name: 'helloTask2') {
    setGroup('immoc') //为Task设置组
    setDescription('task study') // 为Task设置描述
    println 'I am helloTask2'
}


//执行 helloTask2
./gradlew helloTask2

在创建Task的时候添加group可以很快的根据分组查找Task,便于在AS右侧 Gradle 的Panel面板中找到,如下所示:

gradle task中调用python脚本 gradle task执行顺序_xml文件

//在创建Task的时候添加group很有用啊 可以很方便的查找对应的Task
//一般情况下只是在other中去查找
task helloTask3(group: 'immoc', description: 'task study') {
    println 'I am helloTask'
}

//执行 helloTask3
./gradlew helloTask3

gradle task中调用python脚本 gradle task执行顺序_xml文件_02

helloTask 是在 Tasks 的 other 分组中可以找到。 

Task的类型

Gradle中有很多Task类型,比如 Copy, Delete 等类型,具体可以查看Gradle文档,链接如下:

Copy - Gradle DSL Version 7.4

gradle task中调用python脚本 gradle task执行顺序_gradle_03

如上图左侧部分就是列出的Task类型,有AntlrTask,Copy,Delete,Jar等等。下面来看一个具体的例子来学习如何使用特定类型的Task。

//tasktype.gradle
//1.定义自己类型的Task,如Copy类型
task copyDocs(type: Copy) {
    from 'app/src/main/doc'
    into 'build/target/doc'
}

//2.在项目根目录中的build.gradle中引入
apply from: file('tasktype.gradle')

//3.执行 copyDocs 任务
./gradlew copyDocs

//4. 查看结果 
在build/target/doc目录下,可以看到从app/src/main/doc目录拷贝过来的文件
//task如果一般不做处理 则只是在配置阶段执行,并且只处理业务逻辑代码,
//不执行doFirst, doLast闭包中代码
//那么如何在执行阶段中执行?
//可以单独执行该任务 ./gradlew helloTask4,
//就可以执行doFirst或者doLast闭包中的代码
task helloTask4(group: 'immoc', description: 'task study') {
    println 'I am helloTask4' 
    doFirst {
        println 'the task group is ++++++++++++++++++++++++++++++++++++ doFirst' + group
    }
    doFirst {

    }
    doLast {
        println 'the task group is ++++++++++++++++++++++++++++++++++++ doLast' + group
    }
}

helloTask4.doFirst {
    println 'the task description is: ' + description
}
//以上两种方式的doFirst的执行先后顺序 先执行外部的,后执行内部的doFirst

depondsOn 用于指定依赖

我们通常在执行 ./gradlew assemble 命令的时候,会发现有很多的命令被执行,明明只执行了一个任务,但是也有其他任务被执行,这其实就是任务的依赖,表明在执行该任务之前,必须先执行完成其他任务,然后才能执行该任务。那么如何指定这种依赖关系呢?有两种方式,一种是在构建任务的时候,在传参的时候指定依赖的任务名称,另外一种就是利用 dependsOn 函数来指定依赖的任务。

建立依赖方法一:

//config.gradle
task taskX {
    doLast {
        println 'taskX'
    }
}

task taskY {
    doLast{
        println 'taskY'
    }
}

//taskX,taskY中的doLast的闭包代码如果要执行,必须要先执行 ./gradlew taskZ
//因为 taskZ 依赖 taskX, taskY, 且必须执行完 taskX,taskY中的
//doLast闭包代码才能执行 taskZ中的doLast闭包代码
task taskZ(dependsOn:[taskX, taskY]){
    println 'run after taskX,taskY'
    doLast {
        println 'taskZ'
    }
}

建立依赖方法二:

task taskX {
    doLast {
        println 'taskX'
    }
}

task taskY {
    doLast{
        println 'taskY'
    }
}

task taskZ {
    println 'run after taskX,taskY'
    doLast {
        println 'taskZ'
    }
}

//建立依赖
taskZ.dependsOn(taskX, taskY)

//执行输出
./gradlew taskZ
> Task :taskX
taskX

> Task :taskY
taskY

> Task :taskZ
taskZ

建立依赖方法三:

task taskZ {
    dependsOn(taskX, taskY)
    println 'run after taskX,taskY'
    doLast {
        println 'taskZ'
    }
}
//config.gradle
//<<被废弃了 << 和doLast的效果一样
task lib1 {
    doLast {
        println 'lib1'
    }
}

task lib2 {
    doLast {
        println 'lib2'
    }
}


task noLib {
    doLast {
        println 'noLib'
    }
}

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

//执行
./gradlew taskZ

//输出结果
> Task :lib1
lib1

> Task :lib2
lib2

> Task :taskZ
taskZ

xml文件分版本存放不同的文件

比如有一个 releases.xml文件,里面有各个版本App的信息,现在想把这些信息按照版本分别输出到不同的xml文件中,创建一个Task完成按照不同版本输出不同文件的功能。

App项目的根目录下的releases.xml文件,将该文件分版本拷贝到根目录下的release文件夹下。

<?xml version="1.0" encoding="UTF-8" ?>
<releases>
    <release>
        <versionCode>100</versionCode>
        <versionName>1.0.0</versionName>
        <versionInfo>App的第一个版本,上线了一些简单功能</versionInfo>
    </release>
    <release>
        <versionCode>101</versionCode>
        <versionName>1.0.1</versionName>
        <versionInfo>App的第二个版本,版本一Bug修复</versionInfo>
    </release>
    <release>
        <versionCode>102</versionCode>
        <versionName>1.1.0</versionName>
        <versionInfo>App的第三个版本,UI修改</versionInfo>
    </release>
</releases>
//config.gradle
//将release中的release.xml文件内容输出到各自版本的文本中
task handleReleaseFile {
    def srcFile = file('releases.xml')
    def destDir = new File(rootDir.path, 'release/')
    println 'handleReleaseFile destDir '+destDir.absolutePath
    doLast {
        println '开始解析对应的XML文件'
        destDir.mkdir()
        def releases = new XmlParser().parse(srcFile)
        releases.release.each {
            releaseNode ->
                //解析每个release节点的内容
                def name = releaseNode.versionName.text()
                def versionCode = releaseNode.versionCode.text()
                def versionInfo = releaseNode.versionInfo.text()
                //创建文件并写入节点数据
                def destFile = new File(destDir, "release-${name}.txt")
                destFile.withWriter {
                    writer ->
                        writer.write("${name}---->${versionCode}---->${versionInfo}")
                }
        }
    }
}

//在执行handleReleaseFileTest任务之前,先必须执行完成 handleReleaseFile 任务,
//其实就是执行其中的 doLast闭包中的代码
task handleReleaseFileTest(dependsOn: handleReleaseFile) {
    //fileTree获取文件树
    def dir = fileTree(rootDir.path + '/release/')
    doLast {
        dir.each {
            println 'the file name is :' + it.name
        }
        println '输出完成...'
    }
}


//执行
./gradlew handleReleaseFileTest

//输出
> Task :handleReleaseFile
开始解析对应的XML文件

> Task :handleReleaseFileTest
the file name is :release-1.0.1.txt
the file name is :release-1.0.0.txt
the file name is :release-1.1.0.txt
输出完成...

mustRunAfter, shouldRunAfter

//执行顺序指定
task taskBB {
    //mustRunAfter 可以同时指定多个 指定多个和指定单个都是一样的
    //mustRunAfter taskAA
    //shouldRunAfter taskAA
    doLast {
        println 'taskBB'
    }
}

task taskAA {
    doLast {
        println 'taskAA'
        sleep(1500)
    }
}

task taskCC {
    //mustRunAfter 可以同时指定多个 指定多个和指定单个都是一样的
    mustRunAfter taskBB
    //shouldRunAfter taskBB
    doLast {
        println 'taskCC'
    }
}

task taskDD {
    //mustRunAfter taskDD
    doLast {
        println 'taskDD'
    }
}

taskDD.dependsOn(taskBB, taskAA, taskCC)
//shouldRunAfter 不强制执行 和mustRunAfter 差不多 ,但是不常用

通过生命周期函数计算build的时长

//config.gradle
//计算build执行时长
//通过实验证明 下面代码只能在app的build.gradle中调用
//如果在config.gradle中编写如下代码,会如下错误
//Caused by: org.gradle.api.UnknownTaskException: Task with name 'preBuild' not found in root project 'GradleLearn'.
//不过可以通过路径找到app的project对象,然后在在该对象上调用afterEvaluate()方法,
//这样就可以在config.gradle文件中编写如下代码
def startBuildTime, endBuildTime
project('app').afterEvaluate { Project project ->
    //afterEvaluate在gradle配置阶段结束之后执行 保证要找的task已经配置完毕
    def preBuildTask = project.tasks.getByName('preBuild')
    preBuildTask.doFirst {
        startBuildTime = System.currentTimeMillis()
        println 'the startTime is: '+startBuildTime
    }
    def _buildTask = project.tasks.getByName('build')
    _buildTask.doLast {
        endBuildTime = System.currentTimeMillis()
        println "the build time is: ${endBuildTime - startBuildTime}"
    }
}

//执行 
./gradlew build

//输出
> Task :app:build
the build time is: 70990

Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
See https://docs.gradle.org/4.8.1/userguide/command_line_interface.html#sec:command_line_warnings

BUILD SUCCESSFUL in 1m 16s
369 actionable tasks: 356 executed, 13 up-to-date
执行阶段结束之后..

生成xml文件

在build的task结束之后,将应用的版本信息追加到release.xml文件中

1. 首先定义应用版本信息

//config.gradle
ext {
    versionName = "1.1.0"
    versionCode = '110'
    versionInfo = 'App的第二个版本上线了一些基础核心功能'
    destFile = file('release.xml')
    if (destFile != null && !destFile.exists()) {
        destFile.createNewFile()
    }
}

2. 在releaseinfo.gradle 中writeTask挂接build任务结束

注意:releaseinfo.gradle 是在app的build.gradle中引入的

apply plugin: 'com.android.application'
apply plugin: CustomPlugin
// apply plugin: 'plugin_name'   plugin_name 就是前面的properties文件名字
apply plugin: 'net.wequick.small'
apply from: '../releaseinfo.gradle'
//releaseinfo.gradle
//将writeTask挂接到build结束
//注意:这里如果不把releaseinfo.gradle引入到app的build.gradle文件中
//会发生build的task 查找不到的问题,所以需要把该文件引入到app的build.gradle中
//apply from: 'releaseinfo.gradle'
//这里的问题和上面的例子一样,或者可以用project('app').afterEvaluate来处理
this.project.afterEvaluate { Project project ->
    def buildTask = project.tasks.getByName('build')
    if (buildTask ==null){
        throw new GradleException('the build task is not found')
    }
    //将writeTask挂接到buildTask结束
    buildTask.doLast {
        writeTask.execute()
    }
}

3. 编写writeTask

//releaseinfo.gradle

class VersionMsg {
    String versionCode
    String versionName
    String versionInfo
}

//生成文件Task
task writeTask {
    //为Task指定输入
    //this.versionCode,this.versionName,this.versionInfo都是定义
    //在config.gradle的ext中的扩展属性 
    inputs.property('versionCode', this.versionCode)
    inputs.property('versionName', this.versionName)
    inputs.property('versionInfo', this.versionInfo)
    //为Task指定输出
    outputs.file destFile
    doLast {
        //获取输入的数据
        def data = inputs.getProperties()
        //获取本任务的输出文件
        File file = outputs.getFiles().getSingleFile()
        //将map转为成实体对象
        def versionMsg = new VersionMsg(data)
        //将实体对象转为xml格式数据
        def sw = new StringWriter()
        def xmlBuilder = new MarkupBuilder(sw)
        if (file.text != null && file.text.size() <= 0) {
            //文件中没有内容
            xmlBuilder.releases {
                release {
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
            }
            file.withWriter { writer ->
                writer.append(sw.toString())
            }
        } else {
            //已有版本信息
            xmlBuilder.release {
                versionCode(versionMsg.versionCode)
                versionName(versionMsg.versionName)
                versionInfo(versionMsg.versionInfo)
            }
            //将生成的xml数据插入到根节点之前
            def lines = file.readLines()
            def lengths = lines.size() - 1
            file.withWriter { writer ->
                lines.eachWithIndex { String line, int index ->
                    if (index != lengths) {
                        //将新的内容插入到文档最后一行的前一个节点处
                        writer.append(line + "\n")
                    } else if (index == lengths) {
                        writer.append(sw.toString() + "\n")
                        writer.append(lines.get(lengths))
                    }
                }
            }
        }
    }
}

4. 编写readTask 

//releaseinfo.gradle
//读取文件的内容
task readTask {
    inputs.file destFile
    doLast {
        def file = inputs.files.singleFile
        println file.text
    }
}

6. 测试Task

//releaseinfo.gradle
//输出任务比输入任务优先执行
task taskTest {
    dependsOn readTask, writeTask
    doLast {
        println '输入输出任务结束'
    }
}

从上面来看,我们在执行 ./gradlew build的时候,只会执行 writeTask,不会执行 readTask,因为在afterEvaluate中指明了在build任务结束之后,会执行的是writeTask任务。只有在执行../gradlew taskTest 的时候,才会因为该task依赖 readTask,writeTask,并且因为依赖的输入和输出之间的关系,即inputs和outputs之间的先后关系,决定了先执行writeTask,然后执行readTask。不管是执行./gradlew build 还是执行 ./gradlew taskTest 都会执行 writeTask, 最后都会在release.xml 文件中添加或者追加版本信息,如下所示:

//release.xml
<releases>
    <release>
        <versionCode>100</versionCode>
        <versionName>1.0.0</versionName>
        <versionInfo>App的第一个版本上线了一些基础核心功能</versionInfo>
    </release>
<release>
  <versionCode>110</versionCode>
  <versionName>1.1.0</versionName>
  <versionInfo>App的第二个版本上线了一些基础核心功能</versionInfo>
</release>
</releases>