《在飞Android Gradle实战》之核心模块Task3

hi各位小伙伴,上一章《在飞Android Gradle实战》之核心模块Project2介绍了Project的内容。

     Project是脚本代码的入口,所有的脚本代码都是写在project的实例中的,而每一个build.gradle文件就对应一个project类的实例,当然也可以在 build.gradle文件中使用project api去定位文件、获取root工程以及管理子工程,为Project添加依赖等等

前言:

  了解完核心Project,还需要了解另一个核心内容就是Task,Project是脚本的入口,Task就是我们具体的业务逻辑。

  还是一样的风格,只讲实战中经常用到的内容,让你快速入门和使用,加上我自己的分析让你更加清楚的了解这些内容。至于基础知识请自己学习~。

本章难度:简单  普通 困难    

本章重要程度:普通 重要 核心

这篇主要讲Task的相关内容,先从Task定义、配置、Task的解析

一:本章概述:

     当我们在Android Studio的Terminal中执行./gradlew clean,./gradlew build等等,这些命令里面的clean、build就是我们所说的task只是这些是andorid gradle插件已经存在的了。

     当我们执行命令时会看到执行task命令的时候还会有其他task先执行了,然后才会执行我们的clean、build这些Task。所以task执行是有一定顺序的,当然task顺序我们是可以配置的。

     Task执行是在Gradle生命周期的运行阶段执行的关于生命周期可以参考《在飞Android Gradle实战》之生命周期1 build.gradle中的大部分脚本都是运行在配置阶段的,那如何让我们的脚本代码在运行阶段执行呢,当然要将业务逻辑写在task{}闭包中,只有Task能够在运行阶段执行。

     为什么要有Task的依赖和执行顺序? 我们为Task执行顺序,可以根据逻辑执行多个Task以此来完成我们Task中的复杂业务逻辑,完成结果。

二:定义Task和基本配置

第一种:

//1.直接通过task函数创建一个Task,group分组(创建组名后再我们的gradle中就可以看到我们的task了),description是这个task的注释
task helloTask(group:'dap',description:'task study'){
    print "im helloTask...."
}

第二种:

//2.通过project.tasks创建(TaskContainer去创建Task)
this.tasks.create(name:'helloTask2'){
    setGroup('dap')   //调用api设置group
    setDescription('task study')   //调用api设置描述
    println 'i m helloTask2...'
}

经验:

  第一种:直接通过task函数创建一个Task名字叫helloTask,group是分组(创建组名后再我们的gradle中就可以看到我们的task了),description是这个task的描述。

第二种:通过project.tasks创建(TaskContainer去创建Task)helloTask2,然后调用setGroup()、setDescription()相关API设置分组和描述。

因为我们设置了group分组,并且名字叫"dap",在Android Studio的gradle中就可以看到名字叫dap的分组及组内的task。

三、doFirst{}、doLast{}

//1.直接通过task函数创建一个Task,group分组(创建组名后再我们的gradle中就可以看到我们的task了),description是这个task的注释
task helloTask(group:'dap',description:'task study'){
    print "im helloTask...."
    //将代码运行在gradle的执行阶段,doFirst{} doLast{}H
    doFirst {//在已有task之前添加相应逻辑
    }
    doLast {//在已有task之后添加逻辑
    }
}

经验:

     只有执行在 doFirst{}或者doLast{}中的代码才会在Gradle的执行周期中执行。

举例:统计preBuild->build这两个task之间的时间差

//统计build时常的task
def startBuildTime,endBuildTime
this.afterEvaluate {  //配置过程结束,保证要找的task都已经配置完毕,所以在afterEvaluate{}
    Project project ->
        def preBuildTask = project.tasks.getByName('preBuild')//查找preBuild这个task
        preBuildTask.doFirst {//在preBuild 这个Task刚开始的时候添加
            startBuildTime = System.currentTimeMillis()//获得开始时间
            println "start time is ${startBuildTime}"
        }
        def endBuildTask = project.tasks.getByName('build')  //查找build这个task
        endBuildTask.doLast {////在build 这个Task执行完之后
            endBuildTime = System.currentTimeMillis()//获取结束时间
            println "the build time is ${endBuildTime - startBuildTime}" //根据差值获取时常
        }
}

四:Task执行顺序

dependsOn强依赖:为task指定一个或多个依赖形的task,这样的话我们当前task的执行必须依赖于它所依赖的其他task的执行,它才能执行。

通过给task指定输入输出:决定不同task执行不同的执行顺序。

/**
 * task依赖
 */
task taskX {
    doLast {
        println 'taskX..'
    }
}
task taskY {
    doLast {
        println 'taskY..'
    }
}
//Z依赖X,Y
task taskZ(dependsOn: [taskX, taskY]) {//taskZ依赖于taskX和taskY
    doLast {
        println 'taskZ..'
    }
}
//输出结果顺序:taskX taskY taskZ

经验:

     一旦为一个task指定了依赖的task,那么在执行task的时候,会先执行它所依赖的task。



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

}
输出结果:lib1 lib2 taskZ    并不会有nolib这个task

//这种形式也可以指定依赖
//taskZ.denpendsOn(taskX,taskY)

五:Task解析Xml

//解析xml
task handleReleaseFile{
    def srcFile=file('releases.xml')  //获取release.xml文件
    def destDir=new File(this.buildDir,'generated/release/')//解析后的文件存放位置
    doLast{//真正要执行的代码,在运行阶段
        println '开始解析...'
        destDir.mkdir()//创建目录文件
        def releases=new XmlParser().parse(srcFile) //解析xml文件,使用XmlParser()
        releases.release.each{releaseNode ->  //找到子节点release,然后对它遍历
            //解析xml节点
            def name=releaseNode.versionName.text()  //解析versionName内容
            def versionCode=releaseNode.versionCode.text() //解析versionCode内容
            def versionInt=releaseNode.versionInfo.text()  //解析versionInfo内容
            //创建文件并写入数据
            def destFile=new File(destDir,"release-${name}.text")
            destFile.withWriter {writer-> //使用withWriter{}向文件中写入内容
                writer.write("name:${name}->code:${versionCode}->intCode:${versionInt}")
            }
        }
    }
}
//测试handleReleaseFile功能
task handleReleaseFileTest(dependsOn: handleReleaseFile){
    def dir=fileTree(this.buildDir.path+'generated/release/')//获取目录
    doLast{
        println '输出完成'
    }
}

执行./gradlew handleReleaseFileTest

六:生成xml文件,并写入新的数据

ext {
    versionName = '1.0.4'
    versionCode = '104'
    versionInfo = '功能1.0.4'
    destFile = file('releases.xml')
    if (destFile != null && !destFile.exists()) {
        destFile.createNewFile()

    }
}

task readTask {
    inputs.file this.destFile
    doLast {
        def file=inputs.files.singleFile
        println "版本信息结果:${file.text.toString()}"
    }
}
//taskinput、tastOutput  升级版本信息版本
task writeTask {
    //为task指定输入内容个,versionName versionCode versionInfo
    inputs.property('versionName',this.versionName)
    inputs.property('versionCode',this.versionCode)
    inputs.property('versionInfo',this.versionInfo)
    //为Task指定输出文件
    outputs.file destFile
    doLast {//核心业务逻辑
        def data = inputs.getProperties()//将传入的所有字段按map的形式返回回来
        def file = outputs.getFiles().getSingleFile()//创建file,getSingleFile获取files中唯一的文件

        //将map接入xml文件中
        def versionMsg = new VersionMsg(data) //先将map转为实体对象
        //将实体对象转为xml
        def sw = new StringWriter() //使用StringWriter Api
        def xmlBuilder = new groovy.xml.MarkupBuilder(sw) //使用MarkUpBuild,将数据写入到sw中
        if (file.text != null && file.text.length() <= 0) { //文件中没内容,写入数据
            xmlBuilder.releases {
                release {
                    versionCode(versionMsg.versionCode)
                    versionName(versionMsg.versionName)
                    versionInfo(versionMsg.versionInfo)
                }
            }
            file.withWriter { writer ->//file写入数据
                writer.append(sw.toString())
            }
        } else {//文件中已经有了数据
            xmlBuilder.release {//生成relase节点,之后生成versionCode versionName versionInfo数据
                versionCode(versionMsg.versionCode)
                versionName(versionMsg.versionName)
                versionInfo(versionMsg.versionInfo)
            }

            def lines = file.readLines()//获取行数
            def lengths = lines.size() - 1
            file.withWriter { writer ->//写入数据
                lines.eachWithIndex { String line, int index ->//遍历行数。line是每一行的内容 index是行数
                    if (index != lengths) {//之前的内容继续写入
                        writer.append(line + '\r\n')
                    } else if (index == lengths) {//如果是最后一行,写生成的节点信息sw.toString
                        writer.append('\r\r\n' + sw.toString() + '\r\n')
                        writer.append(lines.get(lengths))//再写原有的最后一行数据
                    }
                }
            }
        }
    }
}
class VersionMsg{
    String versionName
    String versionCode
    String versionInfo
}
//测试写入xml
task taskTest{
    dependsOn readTask,writeTask //添加依赖
    doLast{
        println '输入输出任务结束'
    }
}

执行./gradlew taskTest
 

经验:  转载请注明出处:

    虽然writerTask和readTask并没有dependsOn相互依赖,但是他俩都是操作 releases.xml这个文件。对于writerTask来说,realeases.xml就是一个输出文件,对于readTask来说它是一个输入文件。所以writerTask、readTask通过共有的destFile这个共有属性关联在一起。Gradle规定我们的输出属性对应的task会被首先执行(writeTask)及生产这先执行。

七:mustRunAfter    明确指定让我们的task在某个task之后执行

task taskA {
    doLast {
        println 'taskA'
    }
}

task taskB{
    mustRunAfter taskA
    doLast {
        println 'taskB'
    }
}

task taskC {
    mustRunAfter taskB
    doLast {
        println 'taskC'
    }
}

八:挂接Task到生命周期中

//将writTask放到build之后
this.project.afterEvaluate {Project project-> //在配置结束后
    def buildTask = project.tasks.getByName('build') //获取build 这个task
    if(buildTask==null){ //如果是null就报异常
        throw GradleException('the build task is not found')
    }
    buildTask.doLast{//将write放到build之后
        writeTask.execute()  //执行writeTask。就是将我们的writeTask放到原来build之后执行。当我们build完成后,版本信息就自动的更新到releases.xml中了
    }
}

九:引用独立gradle

apply from :this.rootProject.file('xxxx.gradle') //引入root跟目录下的xxxx.gradle文件

十:如何将特定的任务挂接到构建周期中间部分

    

经验:

     自定义的manifestTask必须运行在processManifest之后,但是processManifest又依赖于manifestTask。所以这样就将我们自定义的mainfestTask插入到了processManifest和projectssResources这两个Task之间。

十一:Task类型

     https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

总结经验:

     Task和Project是Gradle最重要的两个核心概念,想要学会用Gradle去编写脚本,必须要掌握这些内容。Task定义有两种方式一种是直接使用task方法创建,另一种是通过task.create()创建。

     Task doFirst{} doLast{}我们之后Gradle有三个生命周期阶段,但是只有写在doFirst{}、doLast{}语句块中的代码才能够执行在我们Gradle的执行阶段。

    Task依赖主要是解决我们多个Task哪个先执行哪个后执行,dependensOn input output mustRunAfter 。多个Task相互配合完成某一个功能。

    挂接到生命周期,主要是如何挂接到开始、结束、构建中间,也是Task执行顺序实际的使用。

    Task实现xml文件的写入、读取。同时进行版本管理。

Ps:我是在飞~~,只有空闲时间更新博客,所以本系列偏快速入门和个人经验分享。主要讲实战中经常用到和我认为重要的内容。所以文章尽量简短,敬请谅解,希望我的博客对你有帮助!本系列暂定阅读者已经有groovy基本知识,如果需要我来说下groovy内容也可以评论中提出,后续单开一章带领大家简单入门下Groovy。

祝各位工作顺利!
有问题可联系q:1660380990邮箱[email protected]

    

发布了72 篇原创文章 · 获赞 15 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/pangzaifei/article/details/87643775