Android-0.gradle task相关简介

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个不同的生命周期,配置期执行期,而配置期的代码永远是先于执行期代码执行的,由于前两句代码在doLastdoFirst方法中,所以,这两名代码的执行是在执行期,因此总是后于最后一句的输出。而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

请注意,dependsOntask添加到依赖项集。因此,依赖于多个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只会调用一次,如下AB 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 taskUI 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 taskunit task之间失去了显式依赖关系 现在,如果我决定只运行UI task - 我的unit task将不会被执行。

mustRunAfter有时会无效,参看https://discuss.gradle.org/t/task-ordering-problem-mustrunafter-finalizer-dependencies/14035

finalizedBy

现在我有运行UI taskunit 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 taskunit 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 {
     		....
    }
}
...

如果appbuild.gradle 想使用copySo2Plugins, 可以在appbuild.gradle中包含plugins.gradle, 如下所示:

apply from: '../plugins.gradle'

../是因为plugins.gradleappbuild.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 加入任务

一种是可以在配置期加,如appbuid.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
    }

另一种在是配置期完成时加,如appbuid.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, yydd
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

  1. 我们在扩展的.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"这个本地模块,不存在,就返回远程依赖。

  1. 所以我们把":vbox-lib/lib"加到settings.gradle中:
include ':vbox-lib/lib'

上传到远程构建前注释掉。

  1. 通过git submodule创建远程子模块, 把vbox代码下载到vbox-lib文件夹中
  2. 依赖:
    implementation rootProject.ext.vBoxDependency()

更多任务回调可以参考: Gradle 使用指南 – Gradle 生命周期

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值