一文了解Gradle 的Task

在 Gradle 中,构建的核心逻辑放在 Task 中。那些看上去非常复杂的构建过程,实际上是由一个个 Task 组成的。比如说 Android 的 apk 打包就是由下图的多个任务实现的,图片来源Android Apk 编译打包流程,了解一下

image.png

创建任务

在 Gradle 中,我们可以通过 create 或者 register 来创建任务。它们的区别主要是,register 方法是懒加载的,而 create 不是。Gradle 7.0 以上的版本不再推荐使用 create 来创建任务,因此这里只介绍使用 register 来创建任务。代码示例如下:

tasks.register("myTask") {
    
    group = "help"
    description = "myTask description"
    
    
    doFirst {

    }
    
    doLast {

    }
}

我们设置了任务的 group 属性为 help;以及 description 属性为 myTask description。效果如下图所示,可以在Android studio 的 Gradle 任务栏找到。

image.png

doFirstdoLast 方法则是编译时所执行的操作。对于一些常见的复制、删除、打压缩包等常见的任务,Gradle 提供了 CopyDeleteZip等类型来方便实现类似的功能。代码示例如下:

tasks.register("copyFiles") {
    from("sourceDirectory")
    into("destinationDirectory")
}

tasks.register("deleteFiles") {
    delete("directoryToDelete")
}

tasks.register("createZip") {
    archiveFileName.set("archive.zip")
    destinationDirectory.set(file("outputDirectory"))
    from("sourceDirectory")
}

需要注意,任务名不能重复,否则执行任务时会报错

自定义任务

如果内部自带的类型无法满足要求,我们也可以自定义一个任务。代码示例如下:

open class MyTask: DefaultTask() {

    @TaskAction
    fun action() {
        
    }
    
}

tasks.register("myTask", MyTask::class) {
    
}

其中 @TaskAction 注解声明的方法,在执行任务时会被执行。当有多个 doFirstdoLast 以及 @TaskAction 声明的方法执行时,doFirst倒序执行;@TaskAction 倒序执行; doLast正序执行。代码示例如下:

open class MyTask: DefaultTask() {

    @TaskAction
    fun action1() {
        println("action 1")
    }

    @TaskAction
    fun action2() {
        println("action 2")
    }

}

tasks.register("myTask", MyTask::class) {
    doFirst {
        println("doFirst 1")
    }
    doFirst {
        println("doFirst 2")
    }

    doLast {
        println("doLast 1")
    }
    doLast {
        println("doLast 2")
    }
}

我们可以使用 ./gradlew taskname 命令来执行任务指定的任务。如果想要一次执行多个任务,可以使用 ./gradlew taskname1 taskname2 ...。效果如下所示:

image.png

任务执行结果

image.png

如上图所示,当我们构建时,会显示任务的执行结果。各种执行结果的意义如下:

  • EXCUTED :表示Task执行
  • UP-TO-DATE :表示Task的输出没有改变。分为四种情况:输入和输出都没有改变;只有输出没有改变;Task没有操作但是有依赖,但依赖的内容是最新的,或者跳过了,或者复用了;Task没有操作,也没有依赖;
  • FOME-CACHE:表示可以从缓存中复用上一次的执行结果。
  • SKIPPED :表示跳过当前任务
  • NO-SOURCE:Task不需要执行。有输入和输出,但没有来源。

任务依赖

在 Gradle 中,我们可以通过 dependsOn finalizedBy mustRunAfter shouldRunAfter 来定义任务的执行顺序。

dependsOn

用于指定一个任务依赖于其他一个或多个任务。当一个任务使用dependsOn声明了对其他任务的依赖时,Gradle 会确保被依赖的任务先执行。代码示例如下:

tasks.register("taskA") {
    doLast {
        println("taskA")
    }
}
tasks.register("taskB") {
    dependsOn("taskA")
    doLast {
        println("taskB")
    }
}

在这个例子中,taskB依赖于taskA,所以在执行taskB之前,Gradle 会先执行taskA。效果如下图所示:

image.png

当同时执行 taskAtaskB 时,效果也是一样的

image.png

finalizedBy

用于指定一个任务在另一个任务完成后执行,无论该任务是否成功执行。它通常用于在一个任务完成后执行一些清理或后续处理操作。简单来说就是指定下一个执行的Task,而 dependsOn 指定的是上一个执行的Task。代码示例如下:

tasks.register("taskA") {
    doLast {
        println("taskA")
    }
}
tasks.register("taskB") {
    finalizedBy("taskA")
    doLast {
        println("taskB")
    }
}

效果如下所示:

image.png

image.png

mustRunAfter 和 shouldRunAfter

mustRunAftershouldRunAfter 只有在 taskA 和 taskB 时才有效果。mustRunAfter 确保同时执行时,taskA 先执行;而 shouldRunAfter 是不确定的,具体区别可以看
What is the difference between mustRunAfter and shouldRunAfter in task ordering in Gradle?

tasks.register("taskA") {
    doLast {
        println("taskA")
    }
}
tasks.register("taskB") {
    mustRunAfter("taskA")
    doLast {
        println("taskB")
    }
}

我们可以通过./gradlew tasks来查看所有的Task,但却看不到Task的依赖关系。要查看Task的依赖关系,我们可以使用task-tree插件

plugins {
    id "com.dorongold.task-tree" version "2.1.1"
}

使用时我们只需要在命令后面加上taskTree就行了

gradle <task 1>...<task N> taskTree

任务异常处理

条件不满足

当一些条件不满足 Task 执行时,我们希望跳过这个任务。代码如下所示:

open class MyTask: DefaultTask() {
    @TaskAction
    fun action() {
        println("$name action")
    }
}

tasks.register("taskA", MyTask::class) {
    onlyIf {
        val parameterValue = providers.gradleProperty("a").get()
        parameterValue == "123"
    }
}

当我们执行任务时,如果设置的参数a不是123就跳过这个任务。在 gradle 中,我们通过 -P 来设置参数,调用 ./gradlew taskA -Pa=123 的效果如下:

image.png

当然我们也可以在 action 中抛出 StopExecutionException 异常,达到跳过当前任务的效果。代码示例如下:

tasks.register("taskA", MyTask::class) {
    doFirst {
        val parameterValue = providers.gradleProperty("a").get()
        if (parameterValue != "123") {
            throw StopExecutionException()
        }
    }
}

如果抛出的是其他的异常,则会让所有的Task都停止,任务执行失败。代码示例如下:

tasks.register("taskA", MyTask::class) {
    doFirst {
        throw IllegalStateException()
    }
}

tasks.register("taskB", MyTask::class) {

}

tasks.register("taskC", MyTask::class) {

}

效果如下图所示:

image.png

如果你想要在抛出其他异常的情况下,继续执行后面的任务,可以使用 --continue 选项,效果如下所示:

image.png

超时

我们可以通过 timeout 属性来设置任务的超时时间。如果Task的运行时间超过指定的时间,则执行该任务的线程将被中断。默认情况下,任务没有超时限制。

tasks.register("taskA", MyTask::class) {
    timeout = Duration.ofSeconds(2)
}

任务方法扩展

在 Gradle 中,我们可以为指定的任务添加扩展方法。代码示例如下:

tasks.register("taskA", MyTask::class) {

}

tasks.findByName("taskA")?.actions?.add(object : Action {
    override fun execute(t: Task) {
        println("expand action function")
    }
})

tasks.findByName("taskA")?.doFirst {
    println("expand doFirst function")
}

效果如下所示:

image.png

任务输入与输出

一个函数有输入和输出,任务也是一样的。我们可以通过 @InputFiles@OutputDirectory 来声明输入和输出的目录。代码示例如下,自定义了一个简单的复制任务 SelfCopyTask

open class SelfCopyTask: DefaultTask() {

    @InputFiles
    var inputFiles: FileCollection? = null

    @OutputDirectory
    var outputDir: Directory? = null

    @TaskAction
    fun action() {
        val inputFiles = inputFiles ?: return
        val outputDir = outputDir ?: return
        val file = inputFiles.singleFile
        if (file.isDirectory) {
            inputFiles.asFileTree.forEach {
                copyFileToDir(it, outputDir)
            }
        } else {
            copyFileToDir(file, outputDir)
        }
    }

    private fun copyFileToDir(file: File, targetDir: Directory) {
        val dest = File(targetDir.asFile, file.name)
        if (!dest.exists()) {
            dest.createNewFile()
        }
        file.copyTo(dest, true)
    }

}

tasks.register("SelfCopyTask", SelfCopyTask::class.java) {
    
    inputFiles = files("from")
    
    outputDir = layout.projectDirectory.dir("to")
}

代码中的files主要用于操作文件集合,而layout.projectDirectory.dir主要用于获取项目根目录路径以便构建相对于项目根目录的文件路径

如果你想在任务中获取到指定任务的输出,可以使用 task.outputs 属性。代码示例如下:

tasks.register("testTaskA") {
    dependsOn("SelfCopyTask")
    doLast {
        val task = tasks.findByName("SelfCopyTask")
        println("hasOutput = " + task?.outputs?.hasOutput)
        task?.outputs?.files?.onEach {
            println(it)
        }
    }
}

除了上面提到的 @InputFiles@OutputDirectory 注解外,其他注解详情可以看 官方文档

如果需要支持增量构建,需要给支持增量构建的输入参数增加 @Incremental 注解,同时action方法需要增加 InputChanges 参数,代码示例如下:

open class SelfCopyTask: DefaultTask() {

    @Incremental
    @InputFiles
    var inputFiles: FileCollection? = null

    @OutputDirectory
    var outputDir: Directory? = null

    @TaskAction
    fun action(inputChanges: InputChanges) {
        
        val isIncremental = inputChanges.isIncremental
        inputChanges.getFileChanges(inputFiles).forEach {
            it.changeType 
        }
        ...
    }

    ...
}

参考

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/22550,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?