博客主页

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

任务创建方式,以及配置

可以使用Project提供的task方法或者通过TaskContainer提供的create方法。

任务名字方式创建任务

通过Project中的task(String name)方法创建任务

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

customTask0就是创建的任务名字,通过gradlew customTask0执行这个任务。

任务名字+一个对该任务配置的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源码可知,可用的配置如下:

配置项的详细讲解:

    // 用于配置任务的描述,默认值: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方法创建任务。

任务访问方式

通过任务名访问

我们创建的任务都会作为Project的一个属性,属性名就是任务名,所以可以直接通过任务名访问该任务。

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()
        }
    }
}

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。