Gradle核心(三):Gradle Task详解及实战

博客主页java

接下来说解Gradle核心Task——任务。主要内容有如何多种方式建立任务,如何访问任务的方法和属性,任务执行过程和实战,任务执行顺序和实战, 任务依赖和实战,任务的输入输出,若是挂载自定义Task到构建过程当中,如何对任务进行分组、排序,以及一些规则性的知识。segmentfault

任务建立方式,以及配置

可使用Project提供的task方法或者经过TaskContainer提供的create方法。网络

任务名字方式建立任务

经过Project中的task(String name)方法建立任务闭包

def customTask0 = task('customTask0')
// 调用任务的doLast 方法,该方法在任务执行阶段执行。
customTask0.doLast {
    println "建立任务方法原型: Task task(String name)"
}

customTask0就是建立的任务名字,经过gradlew customTask0执行这个任务。app

任务名字+一个对该任务配置的Map对象来建立任务

def customTask1 = task(group: 'help', 'customTask1')
customTask1.doLast {
    println "建立任务方法原型: Task task(Map<String, ?> args, String name)"
    println "任务分组: ${customTask1.group}, 任务名字:${customTask1.name}"
    // 任务分组: build, 任务名字:customTask1
}

其中Map参数用来对建立的Task进行配置,上例中指定任务的分组为help,该任务就会分组到help组中。
单元测试

任务名+闭包方式建立任务

// 方式一:建立任务并配置任务
task customTask2 {
    // 配置任务的分组
    group 'myTask'
    // 配置任务的描述信息
    description '任务名+闭包方式建立任务'

    // 处理任务执行后须要作的事
    doLast {
        println "建立方法原型:Task task(String name, Closure configureClosure)"
        println "任务分组:${customTask2.group}, 任务描述:${customTask2.description}"
    }
}

// 方式二:建立任务并配置任务
task customTask2(group: 'myTask', description: '任务名+闭包方式建立任务') {
    doLast {
        println "建立方法原型:Task task(String name, Closure configureClosure)"
        println "任务分组:${customTask2.group}, 任务描述:${customTask2.description}"
    }
}

除了可使用Map参数配置任务,还可使用闭包的方式对任务进行配置。由于闭包中的委托对象就是Task,全部可使用Task对象的任何方法、属性进行配置。测试

查看Task源码可知,可用的配置以下:

配置项的详细讲解:gradle

// 用于配置任务的描述,默认值:null
    String TASK_DESCRIPTION = "description";

    // 用于配置任务的分组,默认值:null
    String TASK_GROUP = "group";

    // 基于一个存在的Task来建立,和咱们类继承差很少,默认值:DefaultTask
    String TASK_TYPE = "type";

    // 用于配置任务的依赖,默认值:[]
    String TASK_DEPENDS_ON = "dependsOn";

    // 是否替换存在的Task,这个和type配合起来用,默认值:false
    String TASK_OVERWRITE = "overwrite";

    // 添加到任务中的一个Action或者一个闭包,默认值:null
    String TASK_ACTION = "action";

经过TaskContainer建立任务

tasks.create('customTask3') {
    group 'myTask'
    description '经过TaskContainer建立任务'

    doLast {
        println "TaskContainer的create建立任务原型:Task create(String name, Closure configureClosure)"
        println "任务分组: ${group}, 任务描述: ${description}"
    }
}

tasksProject对象的属性,类型是TaskContainer,能够直接调用它的create方法建立任务。ui

任务访问方式

经过任务名访问

咱们建立的任务都会做为Project的一个属性,属性名就是任务名,因此能够直接经过任务名访问该任务。this

def customTask0 = task('customTask0')
// 经过任务名访问
customTask0.doLast {
    println "建立任务方法原型: Task task(String name)"
}

经过TaskContainer集合方式访问

其实TaskContainer就是咱们建立任务的集合,在Project中能够经过tasks属性访问TaskContainer,能够经过访问集合的方式访问任务。

tasks['customTask3'].doFirst {
    println "经过访问集合的方式访问任务."
}

经过任务名获取任务,其实调用的就是tasks.getAt('customTask3'),最后调用的是findByName(String name)实现。

经过TaskContainer的find或者get方式访问

get访问方式若是找不到任务,就会抛出UnknownTaskException异常。
find访问方式若是找不到任务,就会返回null

task customTask4

tasks['customTask4'].doLast {
    // find方式访问
    println "经过路径find方式访问: ${tasks.findByPath(':customTask4')}"
    println "经过名称find方式访问: ${tasks.findByName('customTask4')}"

    // get方式访问
    println "经过路径get方式访问: ${tasks.getByPath(':customTask4')}"
    println "经过名称get方式访问: ${tasks.getByName('customTask4')}"
}

任务执行实战

统计执行阶段的时间,也就是全部Task的执行的全部时间。

def startBuildTime, endBuildTime
// afterEvaluate配置阶段完成调用
this.afterEvaluate { Project project ->
    println "-----------配置阶段完成--------------"
    // 全部Task配置完成后,找到第一个执行的Task
    def preBuildTask = project.tasks.findByName('preBuild')
    if (preBuildTask) {
        preBuildTask.doFirst {
            startBuildTime = System.currentTimeMillis()
            println "task build start time: ${startBuildTime}"
        }
    }


    def buildTask = project.tasks.findByName('build')
    if (buildTask) {
        buildTask.doLast {
            endBuildTime = System.currentTimeMillis()
            println "the build cost time: ${endBuildTime - startBuildTime}"
        }
    }
}

任务依赖

上一篇中已经讲解了任务依赖,单个任务和多个任务依赖,能够经过dependsOn指定其依赖的任务。可是咱们也能够经过匹配指定依赖的任务。

task myTask1 {
    doLast {
        println "myTask1>>doLast"
    }
}

task myTask2 {
    doLast {
        println "myTask2>>doLast"
    }
}

task customTask5 {
   // 经过匹配,查看依赖任务
    dependsOn this.project.tasks.findAll { Task task ->
        println "task name>>> ${task.name}"
        return task.name.startsWith('myTask')
    }
    doLast {
        println "customTask5>>doLast"
    }
}

任务依赖-项目实战

将发布版本文档的输出到每一个版本单独文档中实战。

// releases.xml,发布版本文档格式
<releases>
    <release>
        <versionCode>100</versionCode>
        <versionName>1.0.0</versionName>
        <versionInfo>App的第1个版本,上线了一些最基础核心的功能.</versionInfo>
    </release>

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

将解析文档后的内容写入到${buildDir}/generated/release/release-${versionCode}.txt文件中

tasks.create('handleReleaseInfoTask') {
    println "buildDir>>> ${this.buildDir.path}"
    def srcFile = file('releases.xml')
    def destDir = new File(this.buildDir, 'generated/release/')

    doLast {
        println "开始解析releases.xml文件"
        if (!destDir.isDirectory()) destDir.mkdirs()

        def releases = new XmlParser().parse(srcFile)
        releases.release.each { Node releaseNode ->
            def versionCode = releaseNode.versionCode.text()
            def versionName = releaseNode.versionName.text()
            def versionInfo = releaseNode.versionInfo.text()
            // 建立文件写入
            def descFile = new File(destDir, "release-${versionCode}.txt")
            descFile.withWriter { writer ->
                writer.write("${versionCode}->${versionName}->${versionInfo}")
            }
        }
    }
}

// 测试任务handleReleaseInfoTaskTest依赖handleReleaseInfoTask任务
task handleReleaseInfoTaskTest(dependsOn: handleReleaseInfoTask) {
    def fileDir = fileTree("${this.buildDir.path}/generated/release/")

    doLast {
        fileDir.each {
            println "the file name>>> ${it}"
        }
        println "解析完成."
    }
}

任务分组和描述

任务是能够分组和添加描述的,分组就是对任务分类。在经过执行gradlew tasks查看任务信息时,就能够看到不一样组下的任务,并还能够看到任务描述信息。

// 配置任务的分组和描述信息
task customTask6(group: 'myTask', description: '任务分组和描述案例') {
    doLast {
        println "group: ${group}, description: ${description}"
    }
}

添加分组后,能够在组里找到相应的任务,以下图所示:
查看分组任务

输出任务分组和描述信息

<< 操做符(已过期,建议doLast)

<< 操做符是Gradle的Task中的doLast方法的短标记形式,也就是<<代替doLast方法。

task customTask7 << {
    println "customTask7 doLast"
}

其实<<操做符在Groovy中能够重载的,查看源码可知,在Task接口中对应leftShift方法重载了<<操做符。

任务执行流程分析

当执行一个Task时,其实就是执行Task对象中的actions列表,其类型是一个List

task customTask8(type: CustomTask) {

    doFirst {
        println "Task执行以前执行:doFirst"
    }

    doLast {
        println "Task执行以后执行:doLast"
    }
}

class CustomTask extends DefaultTask {
    @TaskAction
    def doSelf() {
        println "Task本身自己在执行:doSelf"
    }
}

> gradlew customTask8

// 执行Task后输出的日志信息
Task执行以前执行:doFirst
Task本身自己在执行:doSelf
Task执行以后执行:doLast

上例中定义一个Task类型CustomTask , 被TaskAction注解标记的方法,表明Task自己执行要执行的方法。
其实doFirst ,doSelf,doLast 这个三个方法可以按照顺序执行,那么在actions列表中必须按照顺序排列的。

在Task建立时,Gradle就会解析被TaskAction标记的方法做为其Task执行的Action,经过actions.add(0, action)添加 到actions列表中。

doFirst方法经过actions.add(0, action)添加到actions列表中,doFirst就会出如今doSelf前面;而doLast经过actions.add(action)添加到actions列表中,doLast就会出如今doSelf后面。因此在执行Task的时,就达到顺序执行的目的。

任务排序

能够经过 mustRunAftershouldRunAfter 方法控制一个任务必须或者应该在某个任务后执行。

taskB.shouldRunAfter(taskA) 表示taskB应该在taskA执行后执行,可能任务顺序不会按照指望的执行。
taskB.mustRunAfter(taskA) 表示taskB必须在taskA执行后执行。

task customTask10 {
    doLast {
        println "TasK: customTask10"
    }
}

task customTask9 {
    mustRunAfter customTask10
    doLast {
        println "TasK: customTask9"
    }
}

> gradlew customTask9 customTask10 

// 执行后输出日志信息
TasK: customTask10
TasK: customTask9

任务的启动和禁用

Task有个enabled属性能够启动和禁用任务。默认为true,表示启动;当设置为false,输出会提示该任务被Skipping。

task customTask11 {
    doLast {
        println "TasK: customTask11"
    }
}
customTask11.enabled = false

> gradlew -i customTask11 

// 输出的日志信息
Skipping task ':customTask11' as task onlyIf is false.

任务的onlyIf断言

断言就是一个条件表达式。Task中有一个onlyIf方法,接受闭包做为参数,当该闭包返回true,该任务执行,不然跳过。
应用场景:能够控制程序哪些状况下打什么包,何时执行单元测试,什么状况下执行单元测试时候不执行网络测试。

案例实战:假设打渠道包时,若是直接build会编译出全部的包,太慢!能够经过onlyIf控制

tasks.create('buildHuaweiRelease') {
    doLast {
        println "build 华为渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildHuaweiRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildMIUIRelease {
    doLast {
        println "build MIUI渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildMIUIRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildQQRelease {
    doLast {
        println "build QQ渠道包."
    }

    onlyIf {
        def execution = true

        if (project.hasProperty('build_apps')) {
            Object build_apps = project.property('build_apps')
            println "buildMIUIRelease>>> build_apps: ${build_apps}"

            if ('all'.equals(build_apps) || 'exclude_shoufa'.equals(build_apps)) {
                execution = true
            } else {
                execution = false
            }
        }

        return execution
    }
}

task buildTask {
    group BasePlugin.BUILD_GROUP
    description '打渠道包'
    dependsOn buildHuaweiRelease, buildMIUIRelease, buildQQRelease
}

上例中buildHuaweiReleasebuildMIUIRelease 是首发渠道包,buildQQRelease 不是首发渠道包,能够经过build_apps属性控制打哪些渠道包

// 打全部渠道包
gradlew buildTask
gradlew -Pbuild_apps=all buildTask

// 打首发渠道包
gradlew -Pbuild_apps=shoufa buildTask

// 打非首发渠道包
gradlew -Pbuild_apps=exclude_shoufa buildTask

命令行中-P意思是:为Project指定K-V格式的属性键值对,格式为:-PK=V

任务添加规则

当执行或者依赖的任务不存在时,添加任务规则后,能够对执行失败的任务作一些操做。

// 任务名做为闭包的参数
tasks.addRule('规则描述') {String taskName ->
    task(taskName) {
        doLast {
            println "${taskName}任务不存在"
        }
    }
}

task ruleTaskTest {
    dependsOn missTask
}

// 执行后属性日志信息
missTask任务不存在

任务输入输出

Task提供了inputsoutputs 输入输出属性。

Task输入输出案例实战:版本发布文档自动维护

步骤:请求本次发布的版本相关信息->将版本相关信息解析出来->将解析出来的数据生成xml格式数据->写入已有的文档数据中

请求版本信息这一步使用自定义属性方式代替,首先定义版本相关信息以下

ext {
    versionCode = 105
    versionName = '1.0.5'
    versionInfo = 'App first version.'
 
    destVersionOutputsFile = this.project.file('releases.xml')
    if (!destVersionOutputsFile.exists()) {
        destVersionOutputsFile.createNewFile()
    }
}

// 用于封装版本信息
class Version {
    def versionCode
    def versionName
    def versionInfo
}

建立一个写入任务writeVersionTask

tasks.create('writeVersionTask') {
    group 'myTask'
    description '版本信息自动写入任务.'

    inputs.property('versionCode', versionCode)
    inputs.property('versionName', versionName)
    inputs.property('versionInfo', versionInfo)

    outputs.file destVersionOutputsFile

    doLast {
        println "版本信息自动写入任务开始."
        def versionData = inputs.getProperties()
        def version = new Version(versionData)

        def writerFile = outputs.files.singleFile

        def sw = new StringWriter()
        def markupBuilder = new MarkupBuilder(sw)

        if (writerFile.text != null && writerFile.text.size() <= 0) {
            // 第一次写入
            markupBuilder.releases {
                markupBuilder.release {
                    versionCode(version.versionCode)
                    versionName(version.versionName)
                    versionInfo(version.versionInfo)
                }
            }

            writerFile.withWriter { Writer writer ->
                writer.write(sw.toString())
            }

        } else {
            // 已有其余版本信息
            markupBuilder.release {
                versionCode(version.versionCode)
                versionName(version.versionName)
                versionInfo(version.versionInfo)
            }

            def lines = writerFile.readLines()
            def linesSize = lines.size()

            writerFile.withWriter { Writer writer ->
                lines.eachWithIndex { line, index ->
                    println "line: ${line}, index: ${index}"
                    if (index != linesSize - 1) {
                        writer.append(line).append('\n')
                    } else {
                        // 最后一行
                        writer.append(sw.toString()).append('\n').append('\n')
                        writer.append(line)
                    }
                }
            }
        }
        println "版本信息自动写入任务结束."
    }
}

建立一个读取任务readVersionTask

tasks.create('readVersionTask') {
    group 'myTask'
    description '版本信息自动读取任务.'

    mustRunAfter writeVersionTask

    inputs.file destVersionOutputsFile

    doLast {
        def readFile = inputs.files.singleFile
        println readFile.text
    }
}

建立一个测试任务versionTaskTest

tasks.create('versionTaskTest') {
    dependsOn writeVersionTask, readVersionTask
    doLast {
        println "版本信息自动维护结束"
    }
}

挂载自定义的Task到构建过程当中

上例中,每次发布版本,都要手动执行writeVersionTask任务,怎么挂载在build构建过程当中呢?

// afterEvaluate:配置阶段完成调用,此时全部的Task解析完成
this.afterEvaluate {
    // 找到build任务
    def buildTask = project.tasks.findByName('build')
    if (buildTask != null) {
        buildTask.doLast {
            // build任务执行完后调writeVersionTask任务
            writeVersionTask.execute()
        }
    }
}

若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)

相关文章
相关标签/搜索