文章目录
task的跟踪基本都是使用println, 比如使用gradlew :lib:assembleDebug --stacktrace可以看到println的结果
1.task基础
1.1.1 task 生命周期
task hello {
doLast{
print "3"
}
doFirst{
print "2"
}
print '1'
}
Task最特殊的一个地方,它会涉及到2个不同的生命周期,配置期和执行期,而配置期的代码永远是先于执行期代码执行的,由于前两句代码在doLast和doFirst方法中,所以,这两名代码的执行是在执行期,因此总是后于最后一句的输出。而doFirst的内容又总是先于doLast执行,所以这三条语句生成顺序为 1 2 3。
1.1.2 Gradle脚本语法
Gradle构建脚本函数是用Groovy编写的,一般建议参考: 中文Groovy教程
task一般都会在内部用到脚本函数。
1.1.3 task依赖
https://trickyandroid.com/gradle-tip-3-tasks-ordering/
dependsOn
一个task的执行时依赖别的task的方法就是使用dependsOn方法, 如:
比如下面的场景,已经存在task A,我们要添加一个task B,它的执行必须要在A执行完之后:

假定A和B的定义如下:
task A { doLast { println 'Hello from A' } }
task B { doLast {println 'Hello from B'} }
B.dependsOn A
这意味着每当我尝试执行任务时B- Gradle也将负责执行任务A:
paveldudka$ gradle B
:A
Hello from A
:B
Hello from B
另外,你也可以在task的配置区中来声明它的依赖:
task A { doLast { println 'Hello from A' } }
task B {
dependsOn A
doLast {
println 'Hello from B'
}
}
如果我们想要在现有的task依赖中插入新的task该怎么做呢?

原始task图:
task A { doLast {println 'Hello from A'}}
task B { doLast {println 'Hello from B'}}
task C { doLast {println 'Hello from C'}}
B.dependsOn A
C.dependsOn B
新的自定义task:
task B1 { doLast {println 'Hello from B1'}}
B1.dependsOn B
C.dependsOn B1
输出:
paveldudka$ gradle C
:A
Hello from A
:B
Hello from B
:B1
Hello from B1
:C
Hello from C
请注意,dependsOn将task添加到依赖项集。因此,依赖于多个task是完全没有问题的:

task B1 { doLast{println 'Hello from B1'}}
B1.dependsOn B
B1.dependsOn Q
输出:
paveldudka$ gradle B1
:A
Hello from A
:B
Hello from B
:Q
Hello from Q
:B1
Hello from B1
特别注意: 被依赖的task只会调用一次,如下A被B C同时依赖:
task A { doLast { println 'Hello from A' } }
task B { doLast {println 'Hello from B'} }
task C { doLast {println 'Hello from C'} }
B.dependsOn A
C.dependsOn A
那么A只会调用一次:
paveldudka$ gradle B C
:A
Hello from A
:B
Hello from B
:C
Hello from C
A只会调用一次这个特性在大量task交互时很容易被忽视, 这个特性在后续关键字中也是类似。
mustRunAfter
现在假定我有一个task,它依赖于其他两个task。这里我使用一个真实的场景,我有两个task,一个是unit task,一个是UI task。另外还有一个全部测试的tests task,它依赖于前面两个task。

task unit { doLast {println 'Hello from unit tests'} }
task ui { doLast {println 'Hello from UI tests'} }
task tests { doLast {println 'Hello from all tests!'} }
tests.dependsOn unit
tests.dependsOn ui
unit task和UI task的执行顺序是不能保证的,由于UI task远比unit task时间长,因此我希望unit task先执行。一个解决办法就是让UI task依赖于unit task。

task unit { doLast {println 'Hello from unit tests'}}
task ui { doLast {println 'Hello from UI tests'}}
task tests { doLast {println 'Hello from all tests!'}}
tests.dependsOn unit
tests.dependsOn ui
ui.dependsOn unit // <-- I added this dependency
输出:
paveldudka$ gradle tests
:unit
Hello from unit tests
:ui
Hello from UI tests
:tests
Hello from all tests!
但!这种方法有一个非常讨厌问题!我的UI task并不真正依赖于unit task。我想能够单独运行UI task,但现在每次我想运行UI task - 我的UI task也将运行!
这时就要用到mustRunAfter了, 它只是告诉Gradle如果两个task同时存在执行的优先级。
task unit{ doLast {println 'Hello from unit tests'}}
task ui{ doLast {println 'Hello from UI tests'}}
task tests{ doLast {println 'Hello from all tests!'}}
tests.dependsOn unit
tests.dependsOn ui
ui.mustRunAfter unit
输出:
paveldudka$ gradle tests
:unit
Hello from unit tests
:ui
Hello from UI tests
:tests
Hello from all tests!
依赖图看起来像:

请注意,我们在UI task和unit task之间失去了显式依赖关系 现在,如果我决定只运行UI task - 我的unit task将不会被执行。
mustRunAfter有时会无效,参看https://discuss.gradle.org/t/task-ordering-problem-mustrunafter-finalizer-dependencies/14035
finalizedBy
现在我有运行UI task和unit task。假设它们都生成测试报告。所以我决定创建一个将2个测试报告合并为一个的task:

task unit{ doLast {println 'Hello from unit tests'} }
task ui{ doLast {println 'Hello from UI tests'} }
task tests{ doLast {println 'Hello from all tests!'} }
task mergeReports{ doLast {println 'Merging test reports'} }
tests.dependsOn unit
tests.dependsOn ui
ui.mustRunAfter unit
mergeReports.dependsOn tests
现在,如果我想获得UI task和unit task的测试报告 - 我执行mergeReports task:
paveldudka$ gradle mergeReports
:unit
Hello from unit tests
:ui
Hello from UI tests
:tests
Hello from all tests!
:mergeReports
Merging test reports
mergeReports从用户的角度来看感觉不是特别好。我希望执行tests task就可以获得测试报告,而不必知道mergeReports的存在, 当然我可以把mergeReports的逻辑挪到tests task中, 但如果tests task 是一个系统task呢?比如assembleRelease, 显然,没法直接在assembleRelease中增加逻辑。
这时就要用到finalizedBy,它为此task添加了最终的task。
让我们修改脚本如下:
task unit{ doLast {println 'Hello from unit tests'}}
task ui{ doLast {println 'Hello from UI tests'}}
task tests{ doLast {println 'Hello from all tests!'}}
task mergeReports{ doLast {println 'Merging test reports'}}
tests.dependsOn unit
tests.dependsOn ui
ui.mustRunAfter unit
tests.finalizedBy mergeReports
现在我执行tests task,我仍然得到我的合并测试报告:
paveldudka$ gradle tests
:unit
Hello from unit tests
:ui
Hello from UI tests
:tests
Hello from all tests!
:mergeReports
Merging test reports
1.1.4 task单独抽离到文件
如果我们的需求非常复杂的情况下,有时 build.gradle 里面的就会充斥很多自定义的 task ,非常的不美观。为了解决这个问题,我们可以单独的抽出一个文件就叫plugins.gradle放在工程的根目录, plugins.gradle来写所有的自定义 task,比如:
def copyApkFile = { srcFile, dstFilePath ->
....
}
task copySo2Plugins() {
doLast {
....
}
}
...
如果app的build.gradle 想使用copySo2Plugins, 可以在app的build.gradle中包含plugins.gradle, 如下所示:
apply from: '../plugins.gradle'
../是因为plugins.gradle在app的build.gradle的上层目录, 注意只有task可以这样跨文件调用, 而方法copyApkFile 就不能这么调用了!!
2.task常用技巧
2.1 取消任务
项目构建过程中那么多任务,有些test相关的任务可能根本不需要,可以直接关掉,在build.gradle中加入如下脚本:
tasks.whenTaskAdded { task ->
if (task.name.contains('AndroidTest')) {
task.enabled = false
}
}
whenTaskAdded后面的闭包会在gradle配置期完成。
2.2 加入任务
一种是可以在配置期加,如app的buid.gradle 中加入如下脚本:
tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
assembleRelease.dependsOn copySo2Plugins
}
}
assembleRelease这个变量只有在task.name == 'assembleRelease'时才能直接使用,同样也可以在全局plugins.gradle中加,针对不同的project区分:
rootProject.subprojects { project ->println("project: ${project.name}")
switch (project.name) {
case "app":
project.tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
assembleRelease.dependsOn copySo2Plugins
}
}
break
}
另一种在是配置期完成时加,如app的buid.gradle 中加入如下脚本:
project.afterEvaluate {
def task = tasks.findByName("assembleRelease")
println("app afterEvaluate: ${task.toString()}")
}
project表示当前模块,要注意tasks.findByName 查找的总是当前模块的task,这里打印的是
app afterEvaluate: task ':app:assembleRelease'
同样也可以在全局plugins.gradle中加,针对不同的project区分:
rootProject.subprojects { project ->
project.afterEvaluate {
def task = tasks.findByName("clean")
println("afterEvaluate: ${task.toString()}")
}
}
这里会依次打印所有的模块的clean任务:
afterEvaluate: task ':app:clean'
afterEvaluate: task ':changelanguage:clean'
afterEvaluate: task ':classloader:clean'
...
有很多关键字,如:project表示当前模块, rootProject表示 根项目, rootDir表示当前项目的根目录。
3.常用的一些方法
3. 1 拷贝函数
def copyFile = { srcFile, dstFilePath ->
def inFile = file(srcFile)
println "inFile " + inFile.absolutePath
if (!inFile.exists()) {
println "inFile " + inFile.absolutePath + "not exist!!"
}
def outFile = new File(dstFilePath)
outFile.getParentFile().mkdirs()
println "outFile " + outFile
outFile.withOutputStream { os -> inFile.withInputStream { ins -> os << ins } }
}
3.2 删除文件夹
def deleteDir
deleteDir = { dirPath ->
def dirFile = file(dirPath)
dirFile.listFiles().each { x ->
if (x.isDirectory()) {
deleteDir(x.getAbsolutePath())
} else {
println("delete ${x.getAbsolutePath()}")
x.delete()
}
}
println("delete ${dirFile.getAbsolutePath()}")
dirFile.delete()
}
或者
task clean(type: Delete) {
delete rootProject.buildDir
}
3.3 全局变量
可以使用全局的map等变量来更方便统一控制,如:
def pluginsMap = ['xx' : 'xx.so',
'yy': 'yy.so',
'dd' : 'dd.so',
]
pluginsMap.each {
// it.key,it.value
}
4.一些特殊的需求
4.1 A模块编译后->拷贝so->编译B模块
最近的一个需求:
app是主模块
各种插件apk模块,如xx, yy,dd
lib是NDK模块,用于编译各种插件apk模块所需的so, 如xx.so, yy.so, dd.so
编译顺序是:1.lib编译完–> 2. 拷贝so到各插件apk模块的jniLibs目录–>3. 各插件apk开始编译打包–>4.app模块将各插件apk收集到一个目录
关键点:
gradlew assembleRelease是一个任务链, 它执行了很多task,最后才执行到assembleRelease。
所以不能让插件的assembleRelease.dependsOn 步骤2 task , 因为lib较大编译很慢, 如果是插件的assembleRelease.dependsOn 步骤2 task ,在调用插件的assembleRelease之前其实已经生成好了插件apk,2步骤task 虽然做了拷贝,但apk中仍没有so, 所以改为插件的mergeReleaseJniLibFolders.dependsOn 步骤2 task 。
代码实现如下:
/**
* 统一控制插件的so打包及拷贝插件apk
*/
def pluginsMap = ['xx' : 'xx.so',
'yy': 'yy.so',
'dd' : 'dd.so',
]
def copyFile = { srcFile, dstFilePath ->
def inFile = file(srcFile)
println "inFile " + inFile.absolutePath
if (!inFile.exists()) {
println "inFile " + inFile.absolutePath + "not exist!!"
}
def outFile = new File(dstFilePath)
outFile.getParentFile().mkdirs()
println "outFile " + outFile
outFile.withOutputStream { os -> inFile.withInputStream { ins -> os << ins } }
}
def copyPluginApk2Target = { pluginName ->
println("copy ${pluginName}.apk to target")
file("${rootDir}/plugins/${pluginName}/build/outputs/apk/release").listFiles().each {
println it
if (it.name.endsWith(".apk")) {
def dstFilePath = file("${rootDir}/app/target/${pluginName}.apk")
copyFile.call(it.getAbsolutePath(), dstFilePath.getAbsolutePath())
}
}
}
def compilePluginApk = { pluginName ->
println("handlePluginApks.dependsOn:${pluginName}.apk")
handlePluginApks.dependsOn(":plugins:${pluginName}:assembleRelease")
}
def copySo2PluginJniLibs = { pluginName, soName ->
println("copy ${soName} to ${pluginName} jniLibs dir")
def srcFile = file("${rootDir}/lib/build/intermediates/transforms/stripDebugSymbol/release/0/lib/armeabi-v7a/${soName}")
def dstFilePath = file("${rootDir}/plugins/${pluginName}/src/main/jniLibs/armeabi-v7a/${soName}")
copyFile.call(srcFile.getAbsolutePath(), dstFilePath.getAbsolutePath())
}
task handlePluginApks() {
// 设置编译插件在handlePluginApks之前生成
pluginsMap.each {
compilePluginApk(it.key)
}
doLast {
// 拷贝插件到target目录
pluginsMap.each {
copyPluginApk2Target(it.key)
}
}
}
task copySo2Plugin() {
doLast {
pluginsMap.each {
copySo2PluginJniLibs(it.key, it.value)
}
}
}
rootProject.subprojects { project ->
switch (project.name) {
case "app":
project.tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
assembleRelease.finalizedBy handlePluginApks
}
}
break
default:
break;
}
if (null != pluginsMap.get(project.name)) {
project.tasks.whenTaskAdded { task ->
if (task.name == 'mergeReleaseJniLibFolders') {
copySo2Plugin.dependsOn(':lib:assembleRelease')
mergeReleaseJniLibFolders.dependsOn copySo2Plugin
}
}
}
}
4.2 本地构建时使用本地化的第三方库
本地构建时使用本地化的第三方库, 服务器构建时使用远程依赖
比如远程子模块叫vbox/lib
- 我们在扩展的
.gradle中加入以下代码:
vBoxDependency = {
def isReleaseBuild = System.env.RELEASE_VERSION ? true : false
def vbox_lib_path = ":vbox-lib/lib"
print " isReleaseBuild = " + isReleaseBuild
print " isReleaseBuild findProject(vbox_lib_path) = " + findProject(vbox_lib_path)
if (!isReleaseBuild && findProject(vbox_lib_path) != null)
return project(vbox_lib_path)
else
return " com.vbox:${virtualbox_lib_version}"
}
System.env.RELEASE_VERSION只有服务器构建时才为true, 所以上面的意思就是,如果是本地运行,就去查找 ":vbox-lib/lib"这个本地模块是否存在,如果存在,就返回":vbox-lib/lib"这个本地模块,不存在,就返回远程依赖。
- 所以我们把
":vbox-lib/lib"加到settings.gradle中:
include ':vbox-lib/lib'
上传到远程构建前注释掉。
- 通过
git submodule创建远程子模块, 把vbox代码下载到vbox-lib文件夹中 - 依赖:
implementation rootProject.ext.vBoxDependency()
更多任务回调可以参考: Gradle 使用指南 – Gradle 生命周期

被折叠的 条评论
为什么被折叠?



