< center>
命令:
gradle tasks
运行结果:
创建第一个task ,可以以输出
hellotask为例子
,这里总结几种常用的任务创建方式
// 调用Project的task关键字创建 Task
task helloTask{
println "hello task"
}
运行结果:
他还有一种变形
// 这里的 << 表示追加的意思, 向hello1 task中追加执行过程
task helloTask<<{
println "hello task"
}
//上面的代码是和下面之中代码是等价的(后面详细讲解)
task helloTask {
doLast{
println "hello task"
}
}
//也可以这样写
task helloTask {
println "我会执行在配置阶段"
}
helloTask.doLast{
println "我会执行在执行阶段"
}
//输出结果
我会执行在配置阶段
我会执行在执行阶段
在上面的几个例子中,Gradle的DSL向我们展示了一种非常自然的风格来创建Task,而事实上这些都只是一种内部DSL,也即必须符合groovy的语法要求。
上面的task关键字实际上是一个方法调用,该方法属于Project。Project中存在多个重载的task()方法。和Ruby等动态语言一样,在调用groovy方法时,我们不用将参数放在括号里面。
以上我们自定义的几个Task都位于TaskContainer中,Project中的tasks属性即表示该TaskContainer。为此,我们可以新建一个Task来显示这些信息:
task printlnAllTask<<{
tasks.eachWithIndex {task,index->
println "${index} ${task.name}"
}
}
执行结果:
0 androidDependencies
1 assemble
2 assembleAndroidTest
3 assembleDebug
...
94 helloTask
95 outputApp1
96 outputApp2
...
128 signingReport
129 sourceSets
130 test
131 testDebugUnitTest
132 testReleaseUnitTest
...
154 uninstallAll
155 uninstallDebugAndroidTest
156 validateSigningDebugAndroidTest
157 verifyReleaseResources
在上文中我们讲到,通过task()方法创建的Task都被存放在了TaskContainer中,而Project又维护了一个TaskContainer类型的属性tasks,那么我们完全可以直接向TaskContainer里面添加Task。
通过TaskContainer的API文档可以发现,TaskContainer向我们提供了大量重载的create()方法用于添加Task。
// 通过TaskContainer 方式创建Task
tasks.create(name: 'hello4') << {
println 'hello4'
}
运行结果:
一个Task除了执行操作之外,还可以包含多个Property,其中有Gradle为每个Task默认定义的Property,比如description,logger等。
另外,每一个特定的Task类型还可以含有特定的Property,比如Copy的from和to等。
当然,我们还可以动态地向Task中加入额外的Property。在执行一个Task之前,我们通常都需要先设定Property的值,Gradle提供了多种方法设置Task的Property值。
属性名称 | 属性描述 |
---|---|
group | task 所在的分组,所有的task将按照这个分组归类,方便我们以后的查看 |
description | 对当前task的描述,方便阅读 |
name | 指定当前task的名称 |
type | 指定当前task的类型 |
dependsOn | 指定当前task与其他task之间的依赖关系 |
overwirte | 重写执行task |
action | 为task配置执行的逻辑 |
创建时候配置
//group : task 所在的分组,所有的task将按照这个分组归类,方便我们以后的查看 ;description : 对当前task的描述,方便阅读
task helloTask(group:"study",description:"task 创建学习"){
println "hello task"
doFirst{
println "doFirst 1"
}
doFirst{
println "doFirst 2"
}
doLast{
println "doLast 1"
}
doLast{
println "doLast 2"
}
}
helloTask.doFirst{
println "doFirst 3"
}
helloTask.doLast{
println "doLast 3"
}
看看所有的task:
闭包中进行配置
task helloTask {
setGroup("Study")
setDescription("创建学习")
println "hello task"
doFirst {
println "doFirst 1"
}
doFirst {
println "doFirst 2"
}
doLast {
println "doLast 1"
}
doLast {
println "doLast 2"
}
}
helloTask.doFirst {
println "doFirst 3"
}
helloTask.doLast {
println "doLast 3"
}
查看所有的task 同样会得如下结果:
注意task属性的配置一定要在任务执行前
gradle的声明周期有三个阶段,分别是: 初始化阶段,配置阶段,执行阶段 三个阶段
初始化对应groovy中的初始化阶段,主要是读取setting文件,获取所有的projec和task
配置阶段主要根据依赖关系组成有线无环图,同时完成task中变量的初始化,方法的执行(简单理解:创建的任务没有放在doFirst和doLast中的代码全部都会在配置阶段执行)
执行阶段,定义在doFirst和doLast 中代码执行在构建后的执行阶段
task helloTask {
setGroup("study")
setDescription("创建学习")
println "我执行在 hello task 的配置阶段"
doFirst {
println "doFirst 1 我执行在 hello task 已有的的task之前"
}
doFirst {
println "doFirst 2 我执行在 hello task 已有的task之前"
}
doLast {
println "doLast 1 我执行在 hello task 已有的task之后"
}
doLast {
println "doLast 2 我执行在 hello task 已有的task之后"
}
}
helloTask.doFirst {
println "doFirst 3 我执行在 hello task 已有的task之前"
}
helloTask.doLast {
println "doLast 3 我执行在 hello task 已有的task之后"
}
执行结果:
综上可见 doFirst后添加的先执行,先添加的后执行;doLast 后添加的后执行
//计算build执行时长
def buildStartTime, buildEndTime
this.afterEvaluate { Project project ->
//保证要找到的task执行完毕
def proBuildTask = project.tasks.getByName("preBuild")
proBuildTask.doFirst {
buildStartTime=System.currentTimeMillis()
println "build start time is ${buildStartTime}"
}
proBuildTask.doLast {
buildEndTime=System.currentTimeMillis()
println "build end time is ${buildEndTime}"
println "the build used time is ${buildEndTime-buildStartTime}"
}
}
执行结果:
上面案例中的知识点比较多:
1.在配置阶段直接获取子工程的任务有很多大几率会不成功(理论上不会成功),配置阶段子类还没有执行配置,无法获取到当前的任务和配置
在配置完成的监听
afterEvaluate
中是可以做到的doFirst方法可以在任务的执行开始往前追加一个事件
doLast 方法可以在任务执行的最后补加一个事件
TaskContainer 本身是一个 set 集合,是一个维护和管理了所有task的集合,可以通过这个集合查找对应的task
在APP project中定义task printName
task printName{
doFirst{
println "在APP project 中打印一句话"
}
}
在rootProject中定义aaa
task aaa {
println "___________________________${this.tasks.findByPath(":app:printName").name}"
doLast{
println "___________________________${this.tasks.findByPath(":app:printName").name}"
print "当前获取到的compileSdkVersion属性是:${this.ext.android.compileSdkVersion}"
}
}
运行结果:
在APP project中定义task printName
task printName{
doFirst{
println "在APP project 中打印一句话"
}
}
在rootProject中定义aaa
task aaa {
//println "___________________________${this.tasks.getByPath(":app:printName").name}"
doLast{
println "___________________________${this.tasks.getByPath(":app:printName").name}"
print "当前获取到的compileSdkVersion属性是:${this.ext.android.compileSdkVersion}"
}
}
运行结果:
将APP中的 printName task去掉重新运行
直接抛出异常
首先了解下,控制task执行顺序的方式有两种
1.通过dependsOn强依赖的方式
2.通过api指定执行顺序
在rootProject中定义几个简单的task
task taskX{
doLast{
println "taskX"
}
}
task taskY{
doLast{
println "taskY"
}
}
task taskZ(dependsOn:[taskX,taskY]){
doLast{
println "taskZ"
}
}
运行结果
task taskX{
doLast{
println "taskX"
}
}
task taskY{
doLast{
println "taskY"
}
}
task taskZ {
setDependsOn([taskX, taskY])
doLast {
println "taskZ"
}
}
运行结果
结论:
taskX和taskY都是优先在taskZ前面执行的,但是taskX和taskY的执行顺序是无法确定的或者说是不可控的
task的执行事先从被依赖的他task开始执行的
被依赖的task一定放在前面定义否则会找不到,报错Could not get unknown property 'taskY' for root project 'ElecticPowerProjectRepair' of type org.gradle.api.Project.
,报错和明显是依赖了还没有初始化的task任务导致的,gradle类似其他脚本,代码的初始化时自上往下配置的,定义在同一个文件前面的代码会优先执行,既然知道了原因,那解决办法就简单了,可以通过监听初始化生命周期来配置:
task taskX {
doLast {
println "taskX"
}
}
task taskZ {
//这里需要找到对应project对他的生命周期进行监听
this.afterEvaluate {
setDependsOn([taskX, taskY])
}
doLast {
println "taskZ"
}
}
task taskY {
doLast {
println "taskY"
}
}
运行结果
task taskX{
doLast{
println "taskX"
}
}
task taskY{
doLast{
println "taskY"
}
}
task taskZ(){
doLast{
println "taskZ"
}
}
tasks.findByPath("taskZ").dependsOn([taskX,taskY])
println "依赖配置完成"
运行结果:
这种使用的场景比较明确,当我们自己编写了一个task,这个task需要完成的一部分工作已经有现成task可以达到这个效果,我们可以通过依赖系统指定已经封装完成的现有task或者第三方库中的task
| 关键字名称 | 作用 | 备注 |使用方法|
|-------------------|---------------|------------|
| mustRunAfter | 指定一个任务必须执行在另一个任务之后 |优先级小于dependsOn属性|taskA.mustRunAfter taskB|
| shouldRunAfter | 指定一个任务应该执行在另一个任务之后 |一般不用|
代码测试,为了方便构建一个taskTest辅助执行
task taskX {
doLast {
println "taskX"
}
}
task taskZ {
doLast {
println "taskZ"
}
}
task taskY {
doLast {
println "taskY"
}
}
taskZ.mustRunAfter([taskX,taskY])
task taskTest{
setDependsOn([taskZ,taskX,taskY])
}
运行结果:
任务依赖在实际开发中的应该用,最近公司有个需求,每次发布版本的时候需要打包,并将最新的版本包和对应的日志信息文件备份到指定目录。随后调用上传任务将apk文件上传到公司的线上服务器
对于上面的小案例我们可以轻松的设计出自己的task流程图,
实战背景: 公司一般会要求我们的apk在发版时,对本次的版本信息做备份记录,并且对于历史的APP版本,我们应该有一个合理的树形结构方便的查看各个版本的变化和维护的内容
最终的输出实现:
功能实现代码
1.在rootProject中准备一个文件夹output并在文件夹中创建文件 releasesMsg.xml,文件中的内容同上图
2.在rootProject的build文件中创建task
/**
* 生成对应版本的版本信息文件
*/
task handleReleaseTask( group: "releaseApp", description: "生成版本历史记录信息文件,方便查看每个版本的变更记录") {
inputs.file logFile
outputs.file versionFile
doLast {
def srcFile = inputs.files.singleFile
def desFile = outputs.files.singleFile
if (!desFile.exists()) {
desFile.mkdirs()
}
def releases = new XmlParser().parse(srcFile)
releases.each { release ->
def versionCode = release.versionCode.text()
def versionName = release.versionName.text()
def versionInfo = release.versionInfo.text()
println "versionCode: ${versionCode} versionName: ${versionName} versionInfo: ${versionInfo}"
def outFile = new File("${desFile.path}\\release-${versionName}.properties")
outFile.withWriter { writer ->
writer.write("versionCode: ${versionCode} versionName: ${versionName} versionInfo: ${versionInfo}")
}
}
}
}
3. 执行代码,得到运行结果
4.文件中的内容
什么是task的输入输出: 加单说就是多个task上一个task的执行结果或者执行产物作为下一个task执行的参数(或者说是传入值)
这样做的意义是什么?
增量式构建
,简单说就是避免重复编译,节约构建时间,感兴趣的同学可以查看gradle构建系列文章
如何进行输入输出
//task的inputs和outputs属性
task writeBuildLogTask(group: "releaseApp", description: "将版本信息记录到指定的历史版本日志中") {
inputs.properties([versionCode: versionCode_,
versionName: versionName_,
versionInfo: versionInfo_])
outputs.file logFile
doLast{
...
}
}
通过查看task源码发现
task的内部其实是维护有一个默认创建的inputs和outputs的
进一步我们看一下这个inputs和outputs所支持的类型
上面我们默认有了一个
releasesMsg.xml
文件在rootProject下的output文件夹下,下面我们以输入输出的方式完成以下步骤
在rootProject定义扩展属性,当然最好是将他们提取到一个单独的文件中
ext {
versionCode_ = 4
versionName_ = "1.0.7"
versionInfo_ = "App 第四个版本,对一直功能进行优化"
println "---------------${rootProject.getProjectDir().path}"
logFile = new File("${rootProject.getProjectDir()}\\output\\releasesMsg.xml")
if (!logFile.getParentFile().exists()) {
logFile.getParentFile().mkdirs()
}
}
对应 writeBuildLogTask
完成将版本信息书写到 releasesMsg.xml
文件中
task writeBuildLogTask(group: "releaseApp", description: "将版本信息记录到指定的历史版本日志中") {
inputs.properties([versionCode: versionCode_,
versionName: versionName_,
versionInfo: versionInfo_])
outputs.file logFile
doFirst {
def properties = inputs.getProperties()
File log = outputs.getFiles().getSingleFile()
//将版本信息转换成实体类
def versionMsgBean = new versionMsg(properties)
def sw = new StringWriter()
def builder = new MarkupBuilder(sw)
if (!log.exists()) {
log.createNewFile()
}
println "+++++++++++++++++"
if (log.text != null) {
//文件中没有数据
if (log.text.length() <= 0) {
println "----------"
builder.releases {
relase {
versionCode(properties.versionCode)
versionName(properties.versionName)
versionInfo(properties.versionInfo)
}
}
log.withWriter("utf-8") { writer ->
writer.write(sw.toString())
}
} else {
builder.release {
versionCode(properties.versionCode)
versionName(properties.versionName)
versionInfo(properties.versionInfo)
}
def lines = log.readLines()
def length = lines.size() - 1
def sBuffer = new StringBuffer()
lines.eachWithIndex { line, index ->
if (index < length) {
sBuffer.append(line).append("\r\n")
} else {
sBuffer.append(sw.toString()).append("\r\n").append(line)
}
println line
}
log.withWriter("utf-8") { writer ->
writer.write(sBuffer.toString())
}
}
} else {
println "log's text is null"
}
}
}
完成后生成如图文件:
读取历史版本文件生成每个版本的版本信息文件,对应task
/**
* 生成对应版本的版本信息文件
*/
task handleReleaseTask( group: "releaseApp", description: "生成版本历史记录信息文件,方便查看每个版本的变更记录") {
inputs.file logFile
outputs.file versionFile
doLast {
def srcFile = inputs.files.singleFile
def desFile = outputs.files.singleFile
if (!desFile.exists()) {
desFile.mkdirs()
}
def releases = new XmlParser().parse(srcFile)
releases.each { release ->
def versionCode = release.versionCode.text()
def versionName = release.versionName.text()
def versionInfo = release.versionInfo.text()
println "versionCode: ${versionCode} versionName: ${versionName} versionInfo: ${versionInfo}"
def outFile = new File("${desFile.path}\\release-${versionName}.properties")
outFile.withWriter { writer ->
writer.write("versionCode: ${versionCode} versionName: ${versionName} versionInfo: ${versionInfo}")
}
}
}
}
添加依赖关系
handleReleaseTask.mustRunAfter writeBuildLogTask
构建一个辅助执行的 testTask 目的方便我们这两个task的执行
task testTask {
dependsOn writeBuildLogTask, handleReleaseTask
doFirst {
println “---------程序构建完成--------”
}
}
####总结: ####
上面的构建例子包含了很多详细的知识点
1. 到我们task指定了输入输出的时候,如果输入量 (inputs) 和输出量 (outputs) 都没有改变时这个task的配置代码部分将会执行,但是这个代码的action部分将不会被执行(doFirst和doLast),这时候我们在执行task时对应的task将会被标记为
1.同样是上面的这个例子,执行testTask,第一次执行在控制台上输出的结果是
2.第二次执行的结果是
很明显,增加了一行输出,Task :handleReleaseTask UP-TO-DATE
,这个表明我们在第二次执行上面的这个task的时候,他得inputs和outputs变量都没有改变和第一次执行时的输入量相同并且输出文件的结果完全一致,这时候系统将会认为这个task没有必要再次执行(其实是gradle语言的优化机制增量构建),如果我们想让这个gradle的action再次执行我们应该怎么办呢,很简单,修改输入参数的值(文件的话就修改这个文件的内容即可)
这里修改一下输出文件的内容,加一个空格输出结果变成了
很明显,action在此被执行了
其实系统很多的构建都是采用这种方式进行增量的,在我们以后的开发中可以多学习使用这种方式,减少构建消耗的时间。
/**
* 监听gradle配置阶段执行完成,可以抱枕我们可以获取到project下的所有task
*/
rootProject.afterEvaluate { project ->
println "------------${project.name}"
def taskClean = project.tasks.findByPath("clean")
println "${taskClean.name}"
if(taskClean==null){
throw new GradleException("the task clean is not found.")
}
//将testTask追加在clean的后面
taskClean.doLast {
taskZ.execute()
println "---------taskClean.doLast --------------------"
}
}
task taskZ {
doLast {
println "taskZ"
}
}
和心部分就是监听任务配置完成,找到指定的task,然后在其action中追加我们自己的task
执行结果:
执行成功
但是执行taskTest这种带有依赖的task会和我们想像的结果存在出入
task taskTest {
setDependsOn([taskZ, taskX, taskY])
doLast{
println "------------taskTest-----------"
}
}
执行结果为:
依赖的taskZ, taskX, taskY三个task并没有执行。
明白了挂在的使用,我们可以很容易的将上面生成版本的task挂在到工程的构建过程build中去
/**
* 监听gradle配置阶段执行完成,可以抱枕我们可以获取到project下的所有task
*/
rootProject.afterEvaluate { project ->
println "------------${project.name}"
def taskClean = project.tasks.findByPath("clean")
println "${taskClean.name}"
if (taskClean == null) {
throw new GradleException("the task clean is not found.")
}
//将testTask追加在clean的后面
taskClean.doLast {
writeBuildLogTask.execute()
handleReleaseTask.execute()
println "---------taskClean.doLast --------------------"
}
}
运行结果,很明显成功了。但是dependsOn 和 must属性配置的task执行顺序没有了,execute()的执行优先级将会更高,所有交换两个task的位置将会报错
请查看我的gradle中task 依赖关系查看这篇文章