一、学习目标
- Task相关
- Task定义及配置
- Task的执行顺序
- Task类型
- Task挂接到构建生命周期
- Task实战
二、Task实战和详解
Task定义
查看项目中有多少 Task
./gradlew tasks
创建Task
创建方法一:利用Task函数来创建
//config.gradle
task helloTask {
println 'I am helloTask'
}
task('taskOne'){
println 'taskOne'
}
//执行 helloTask
./gradlew helloTask
//执行 taskOne
./gradlew taskOne
创建方法二:利用TaskContainer来创建Task
//config.gradle
this.tasks.create(name: 'helloTask2') {
setGroup('immoc') //为Task设置组
setDescription('task study') // 为Task设置描述
println 'I am helloTask2'
}
//执行 helloTask2
./gradlew helloTask2
在创建Task的时候添加group可以很快的根据分组查找Task,便于在AS右侧 Gradle 的Panel面板中找到,如下所示:
//在创建Task的时候添加group很有用啊 可以很方便的查找对应的Task
//一般情况下只是在other中去查找
task helloTask3(group: 'immoc', description: 'task study') {
println 'I am helloTask'
}
//执行 helloTask3
./gradlew helloTask3
helloTask 是在 Tasks 的 other 分组中可以找到。
Task的类型
Gradle中有很多Task类型,比如 Copy, Delete 等类型,具体可以查看Gradle文档,链接如下:
如上图左侧部分就是列出的Task类型,有AntlrTask,Copy,Delete,Jar等等。下面来看一个具体的例子来学习如何使用特定类型的Task。
//tasktype.gradle
//1.定义自己类型的Task,如Copy类型
task copyDocs(type: Copy) {
from 'app/src/main/doc'
into 'build/target/doc'
}
//2.在项目根目录中的build.gradle中引入
apply from: file('tasktype.gradle')
//3.执行 copyDocs 任务
./gradlew copyDocs
//4. 查看结果
在build/target/doc目录下,可以看到从app/src/main/doc目录拷贝过来的文件
//task如果一般不做处理 则只是在配置阶段执行,并且只处理业务逻辑代码,
//不执行doFirst, doLast闭包中代码
//那么如何在执行阶段中执行?
//可以单独执行该任务 ./gradlew helloTask4,
//就可以执行doFirst或者doLast闭包中的代码
task helloTask4(group: 'immoc', description: 'task study') {
println 'I am helloTask4'
doFirst {
println 'the task group is ++++++++++++++++++++++++++++++++++++ doFirst' + group
}
doFirst {
}
doLast {
println 'the task group is ++++++++++++++++++++++++++++++++++++ doLast' + group
}
}
helloTask4.doFirst {
println 'the task description is: ' + description
}
//以上两种方式的doFirst的执行先后顺序 先执行外部的,后执行内部的doFirst
depondsOn 用于指定依赖
我们通常在执行 ./gradlew assemble 命令的时候,会发现有很多的命令被执行,明明只执行了一个任务,但是也有其他任务被执行,这其实就是任务的依赖,表明在执行该任务之前,必须先执行完成其他任务,然后才能执行该任务。那么如何指定这种依赖关系呢?有两种方式,一种是在构建任务的时候,在传参的时候指定依赖的任务名称,另外一种就是利用 dependsOn 函数来指定依赖的任务。
建立依赖方法一:
//config.gradle
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast{
println 'taskY'
}
}
//taskX,taskY中的doLast的闭包代码如果要执行,必须要先执行 ./gradlew taskZ
//因为 taskZ 依赖 taskX, taskY, 且必须执行完 taskX,taskY中的
//doLast闭包代码才能执行 taskZ中的doLast闭包代码
task taskZ(dependsOn:[taskX, taskY]){
println 'run after taskX,taskY'
doLast {
println 'taskZ'
}
}
建立依赖方法二:
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast{
println 'taskY'
}
}
task taskZ {
println 'run after taskX,taskY'
doLast {
println 'taskZ'
}
}
//建立依赖
taskZ.dependsOn(taskX, taskY)
//执行输出
./gradlew taskZ
> Task :taskX
taskX
> Task :taskY
taskY
> Task :taskZ
taskZ
建立依赖方法三:
task taskZ {
dependsOn(taskX, taskY)
println 'run after taskX,taskY'
doLast {
println 'taskZ'
}
}
//config.gradle
//<<被废弃了 << 和doLast的效果一样
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task noLib {
doLast {
println 'noLib'
}
}
task taskZ {
//依赖所有以lib开头的task
dependsOn this.tasks.findAll {
task -> return task.name.startsWith('lib')
}
doLast {
println 'taskZ'
}
}
//执行
./gradlew taskZ
//输出结果
> Task :lib1
lib1
> Task :lib2
lib2
> Task :taskZ
taskZ
xml文件分版本存放不同的文件
比如有一个 releases.xml文件,里面有各个版本App的信息,现在想把这些信息按照版本分别输出到不同的xml文件中,创建一个Task完成按照不同版本输出不同文件的功能。
App项目的根目录下的releases.xml文件,将该文件分版本拷贝到根目录下的release文件夹下。
<?xml version="1.0" encoding="UTF-8" ?>
<releases>
<release>
<versionCode>100</versionCode>
<versionName>1.0.0</versionName>
<versionInfo>App的第一个版本,上线了一些简单功能</versionInfo>
</release>
<release>
<versionCode>101</versionCode>
<versionName>1.0.1</versionName>
<versionInfo>App的第二个版本,版本一Bug修复</versionInfo>
</release>
<release>
<versionCode>102</versionCode>
<versionName>1.1.0</versionName>
<versionInfo>App的第三个版本,UI修改</versionInfo>
</release>
</releases>
//config.gradle
//将release中的release.xml文件内容输出到各自版本的文本中
task handleReleaseFile {
def srcFile = file('releases.xml')
def destDir = new File(rootDir.path, 'release/')
println 'handleReleaseFile destDir '+destDir.absolutePath
doLast {
println '开始解析对应的XML文件'
destDir.mkdir()
def releases = new XmlParser().parse(srcFile)
releases.release.each {
releaseNode ->
//解析每个release节点的内容
def name = releaseNode.versionName.text()
def versionCode = releaseNode.versionCode.text()
def versionInfo = releaseNode.versionInfo.text()
//创建文件并写入节点数据
def destFile = new File(destDir, "release-${name}.txt")
destFile.withWriter {
writer ->
writer.write("${name}---->${versionCode}---->${versionInfo}")
}
}
}
}
//在执行handleReleaseFileTest任务之前,先必须执行完成 handleReleaseFile 任务,
//其实就是执行其中的 doLast闭包中的代码
task handleReleaseFileTest(dependsOn: handleReleaseFile) {
//fileTree获取文件树
def dir = fileTree(rootDir.path + '/release/')
doLast {
dir.each {
println 'the file name is :' + it.name
}
println '输出完成...'
}
}
//执行
./gradlew handleReleaseFileTest
//输出
> Task :handleReleaseFile
开始解析对应的XML文件
> Task :handleReleaseFileTest
the file name is :release-1.0.1.txt
the file name is :release-1.0.0.txt
the file name is :release-1.1.0.txt
输出完成...
mustRunAfter, shouldRunAfter
//执行顺序指定
task taskBB {
//mustRunAfter 可以同时指定多个 指定多个和指定单个都是一样的
//mustRunAfter taskAA
//shouldRunAfter taskAA
doLast {
println 'taskBB'
}
}
task taskAA {
doLast {
println 'taskAA'
sleep(1500)
}
}
task taskCC {
//mustRunAfter 可以同时指定多个 指定多个和指定单个都是一样的
mustRunAfter taskBB
//shouldRunAfter taskBB
doLast {
println 'taskCC'
}
}
task taskDD {
//mustRunAfter taskDD
doLast {
println 'taskDD'
}
}
taskDD.dependsOn(taskBB, taskAA, taskCC)
//shouldRunAfter 不强制执行 和mustRunAfter 差不多 ,但是不常用
通过生命周期函数计算build的时长
//config.gradle
//计算build执行时长
//通过实验证明 下面代码只能在app的build.gradle中调用
//如果在config.gradle中编写如下代码,会如下错误
//Caused by: org.gradle.api.UnknownTaskException: Task with name 'preBuild' not found in root project 'GradleLearn'.
//不过可以通过路径找到app的project对象,然后在在该对象上调用afterEvaluate()方法,
//这样就可以在config.gradle文件中编写如下代码
def startBuildTime, endBuildTime
project('app').afterEvaluate { Project project ->
//afterEvaluate在gradle配置阶段结束之后执行 保证要找的task已经配置完毕
def preBuildTask = project.tasks.getByName('preBuild')
preBuildTask.doFirst {
startBuildTime = System.currentTimeMillis()
println 'the startTime is: '+startBuildTime
}
def _buildTask = project.tasks.getByName('build')
_buildTask.doLast {
endBuildTime = System.currentTimeMillis()
println "the build time is: ${endBuildTime - startBuildTime}"
}
}
//执行
./gradlew build
//输出
> Task :app:build
the build time is: 70990
Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
See https://docs.gradle.org/4.8.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 1m 16s
369 actionable tasks: 356 executed, 13 up-to-date
执行阶段结束之后..
生成xml文件
在build的task结束之后,将应用的版本信息追加到release.xml文件中
1. 首先定义应用版本信息
//config.gradle
ext {
versionName = "1.1.0"
versionCode = '110'
versionInfo = 'App的第二个版本上线了一些基础核心功能'
destFile = file('release.xml')
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
}
}
2. 在releaseinfo.gradle 中writeTask挂接build任务结束
注意:releaseinfo.gradle 是在app的build.gradle中引入的
apply plugin: 'com.android.application'
apply plugin: CustomPlugin
// apply plugin: 'plugin_name' plugin_name 就是前面的properties文件名字
apply plugin: 'net.wequick.small'
apply from: '../releaseinfo.gradle'
//releaseinfo.gradle
//将writeTask挂接到build结束
//注意:这里如果不把releaseinfo.gradle引入到app的build.gradle文件中
//会发生build的task 查找不到的问题,所以需要把该文件引入到app的build.gradle中
//apply from: 'releaseinfo.gradle'
//这里的问题和上面的例子一样,或者可以用project('app').afterEvaluate来处理
this.project.afterEvaluate { Project project ->
def buildTask = project.tasks.getByName('build')
if (buildTask ==null){
throw new GradleException('the build task is not found')
}
//将writeTask挂接到buildTask结束
buildTask.doLast {
writeTask.execute()
}
}
3. 编写writeTask
//releaseinfo.gradle
class VersionMsg {
String versionCode
String versionName
String versionInfo
}
//生成文件Task
task writeTask {
//为Task指定输入
//this.versionCode,this.versionName,this.versionInfo都是定义
//在config.gradle的ext中的扩展属性
inputs.property('versionCode', this.versionCode)
inputs.property('versionName', this.versionName)
inputs.property('versionInfo', this.versionInfo)
//为Task指定输出
outputs.file destFile
doLast {
//获取输入的数据
def data = inputs.getProperties()
//获取本任务的输出文件
File file = outputs.getFiles().getSingleFile()
//将map转为成实体对象
def versionMsg = new VersionMsg(data)
//将实体对象转为xml格式数据
def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw)
if (file.text != null && file.text.size() <= 0) {
//文件中没有内容
xmlBuilder.releases {
release {
versionCode(versionMsg.versionCode)
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
}
file.withWriter { writer ->
writer.append(sw.toString())
}
} else {
//已有版本信息
xmlBuilder.release {
versionCode(versionMsg.versionCode)
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
//将生成的xml数据插入到根节点之前
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { String line, int index ->
if (index != lengths) {
//将新的内容插入到文档最后一行的前一个节点处
writer.append(line + "\n")
} else if (index == lengths) {
writer.append(sw.toString() + "\n")
writer.append(lines.get(lengths))
}
}
}
}
}
}
4. 编写readTask
//releaseinfo.gradle
//读取文件的内容
task readTask {
inputs.file destFile
doLast {
def file = inputs.files.singleFile
println file.text
}
}
6. 测试Task
//releaseinfo.gradle
//输出任务比输入任务优先执行
task taskTest {
dependsOn readTask, writeTask
doLast {
println '输入输出任务结束'
}
}
从上面来看,我们在执行 ./gradlew build的时候,只会执行 writeTask,不会执行 readTask,因为在afterEvaluate中指明了在build任务结束之后,会执行的是writeTask任务。只有在执行../gradlew taskTest 的时候,才会因为该task依赖 readTask,writeTask,并且因为依赖的输入和输出之间的关系,即inputs和outputs之间的先后关系,决定了先执行writeTask,然后执行readTask。不管是执行./gradlew build 还是执行 ./gradlew taskTest 都会执行 writeTask, 最后都会在release.xml 文件中添加或者追加版本信息,如下所示:
//release.xml
<releases>
<release>
<versionCode>100</versionCode>
<versionName>1.0.0</versionName>
<versionInfo>App的第一个版本上线了一些基础核心功能</versionInfo>
</release>
<release>
<versionCode>110</versionCode>
<versionName>1.1.0</versionName>
<versionInfo>App的第二个版本上线了一些基础核心功能</versionInfo>
</release>
</releases>