Task类型与自定义:Gradle内置Task类型与自定义Task类

说实话,刚开始学Gradle的时候,我对Task的理解特别肤浅。就觉得Task嘛,不就是一段脚本,跑一下就完事了。后来在项目里踩了不少坑,才慢慢摸清楚这里面的门道。今天咱们就好好聊聊Gradle里的Task类型,以及怎么自定义自己的Task。

Gradle内置的Task类型

Gradle给我们准备了好几种现成的Task类型。我个人觉得,用好这些内置类型,能省下不少重复造轮子的时间。咱们挑几个最常用的来说说。

Copy类型

这个我用的最多。说白了就是复制文件用的。我在项目里经常用它来把构建产物复制到指定目录。

tasks.register('copyApk', Copy) {
    from layout.buildDirectory.dir("outputs/apk/debug")
    into layout.buildDirectory.dir("myApks")
    include "**/*.apk"
    exclude "**/*-unaligned.apk"
}

嗯,这里要注意一点:frominto的路径写法。我刚开始用的时候,直接写字符串路径,结果在不同机器上老是出问题。后来改用layout.buildDirectory这种Project提供的API,就稳多了。

Delete类型

删除文件或目录用的。这个其实挺简单,但有个坑我得提醒你。

tasks.register('cleanTemp', Delete) {
    delete layout.buildDirectory.dir("temp")
    // 注意:这里不会删除项目根目录下的文件!
}

我曾经犯过一个错误:想删除项目根目录下的某个文件夹,结果写成了delete "build/temp"。你猜怎么着?它删的是当前工作目录下的,不是项目根目录。后来我学乖了,一律用layout.projectDirectory来定位。

Exec类型

执行系统命令用的。这个在需要调用外部工具时特别有用。

tasks.register('runLint', Exec) {
    workingDir = layout.projectDirectory.asFile
    commandLine 'lint', '--check', 'all'
    // 或者用executable + args的方式
    executable 'lint'
    args '--check', 'all'
}

我个人习惯用commandLine,因为写起来更直观。但要注意,如果命令路径包含空格,最好用commandLine的列表形式,别用字符串拼接。

自定义Task类

内置类型虽然方便,但有时候我们需要更复杂的逻辑。这时候就得自己写Task类了。

自定义Task类有两种方式:一种是在build.gradle.kts里直接写,另一种是单独写一个类文件。我建议你,如果逻辑超过20行,就单独写个类文件,不然build脚本会变得又臭又长。

abstract class MyCustomTask : DefaultTask() {

    @get:Input
    abstract val inputFile: DirectoryProperty

    @get:Output
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun process() {
        val input = inputFile.get().asFile
        val output = outputFile.get().asFile
        
        logger.lifecycle("开始处理: ${input.name}")
        
        // 这里写你的业务逻辑
        input.readLines().forEach { line ->
            output.appendText("处理后的: $line\n")
        }
        
        logger.lifecycle("处理完成: ${output.name}")
    }
}

看到@TaskAction了吗?这个注解告诉Gradle:这个方法就是Task要执行的核心逻辑。没有这个注解,Gradle不知道你的Task该干啥。

@TaskAction注解的细节

这个注解其实挺讲究的。一个Task类里可以有多个@TaskAction方法吗?答案是:不行。Gradle只认一个。

注意:@TaskAction方法必须是public的,不能有返回值(void),也不能有参数。如果你需要传参数,用属性(Property)的方式。

我曾经看到有人这么写:

// 错误示范
@TaskAction
fun process(input: String): Boolean {
    // 这样写会报错!
}

嗯,这样写Gradle会直接给你抛异常。正确的做法是把输入输出都声明为属性。

Task的group与description

这两个属性看着简单,但用好了能让你的构建脚本清晰很多。

属性 作用 示例
group Task所属的分组,用于在任务列表中归类 "build", "verification", "myCustom"
description Task的描述,方便其他人理解 "生成APK并复制到指定目录"
tasks.register('myTask') {
    group = "myCustom"
    description = "这是我自定义的任务,用于处理一些特殊逻辑"
    
    doLast {
        // 任务逻辑
    }
}

你想想看,如果项目里有几十个Task,没有group和description,别人怎么知道哪个是干嘛的?我见过一个项目,所有Task的group都是空的,description也是空的。每次新人来了都得问:"这个task是干啥的?" 后来我花了一天时间,把所有Task的group和description都补上了。从那以后,团队效率提升了不少。

知识体系总览

下面这张图帮你理清今天讲的内容:

Gradle Task类型与自定义 内置Task类型 Copy / Delete / Exec 自定义Task类 继承DefaultTask @TaskAction注解 标记执行方法 Task的group与description 归类与描述,提升可读性 核心要点:用好内置类型,自定义时注意@TaskAction规范,别忘了group和description

小技巧:在命令行里运行 gradle tasks --group="myCustom" 可以只看某个分组的Task。这个命令我几乎天天用,特别是在项目Task特别多的时候。

好了,今天的内容就到这里。记住,Task是Gradle构建的核心,用好内置类型能省力,自定义Task能解决复杂需求,而group和description则是让代码更易维护的关键。下次咱们聊聊Task的依赖关系和执行顺序,这个在大型项目里特别重要。


公众号:蓝海资料掘金营,微信deep3321