接下来讲解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}"
}
}
tasks是Project对象的属性,类型是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的时,就达到顺序执行的目的。
任务排序
可以通过 mustRunAfter 和 shouldRunAfter 方法控制一个任务必须或者应该在某个任务后执行。
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
}
上例中buildHuaweiRelease 和 buildMIUIRelease 是首发渠道包,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提供了inputs 和 outputs 输入输出属性。
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()
}
}
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。