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邮箱1660380990@qq.com