文章目录
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 生命周期