11、自定义Task与Plugin:Gradle Task创建方式(doLast/doFirst/type)、自定义Plugin开发(buildSrc/独立工程)、Extension扩展配置
说实话,Gradle 构建脚本写多了,你会发现重复的 Task 逻辑越来越多。这时候就该考虑自定义 Task 和 Plugin 了。我刚开始接触 Gradle 时,总觉得「写个 Task 不就行了?搞什么 Plugin 啊」。直到项目越来越大,十几个模块都要做同样的签名、混淆、渠道包操作……嗯,那时候我才意识到,没有抽象和复用,维护成本会高到让你想砸电脑。
这一章,我们就来聊聊怎么优雅地创建 Task,怎么开发自己的 Plugin,以及怎么用 Extension 让配置变得灵活。说白了,就是让你从「脚本仔」进化成「构建架构师」。
11.1 Task 的三种创建方式
Gradle 里创建 Task 的方式很多,但最常用的就三种。我个人习惯把它们分成「轻量级」「标准级」「定制级」。
11.1.1 直接用 doLast / doFirst
这是最直接的方式。适合临时调试、快速验证想法。你不需要定义 Task 类型,直接写闭包就行。
tasks.register('helloTask') {
doLast {
println 'Hello, Gradle!'
}
doFirst {
println '我先执行'
}
}
执行顺序:doFirst 先跑,doLast 后跑。我在项目中经常用 doLast 来打印构建耗时、输出 APK 路径等信息。比如:
tasks.whenTaskAdded { task ->
task.doLast {
println "${task.name} 执行完毕,耗时: ${System.currentTimeMillis()}"
}
}
11.1.2 通过 type 指定 Task 类型
Gradle 内置了很多 Task 类型,比如 Copy、Delete、Exec、Zip 等。你只需要指定 type,然后配置属性即可。这比手写 doLast 要安全得多。
tasks.register('backupApk', Copy) {
from layout.buildDirectory.dir('outputs/apk/debug')
into layout.buildDirectory.dir('backup')
include '*.apk'
}
我曾经犯过一个错误:用 doLast 写文件拷贝,结果每次构建都报「文件被占用」。后来换成 Copy 类型,Gradle 自动处理了输入输出依赖,问题就解决了。你想想看,内置类型帮你处理了增量构建、文件锁定这些脏活,何必自己造轮子?
| 内置 Task 类型 | 用途 | 常用属性 |
|---|---|---|
| Copy | 文件拷贝 | from, into, include, exclude |
| Delete | 删除文件/目录 | delete |
| Exec | 执行系统命令 | commandLine, workingDir |
| Zip | 打包成 zip | from, archiveName, destinationDir |
| JavaExec | 运行 Java 程序 | main, classpath, args |
11.1.3 自定义 Task 类
当逻辑复杂到一定程度,比如需要多个输入输出、需要支持增量构建,那就得写一个自定义 Task 类了。我建议放在 buildSrc 里,或者独立工程中。
abstract class MyTask extends DefaultTask {
@Input
abstract Property<String> getMessage()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void doAction() {
outputFile.get().asFile.writeText(message.get())
logger.lifecycle("写入完成: ${message.get()}")
}
}
tasks.register('myTask', MyTask) {
message = 'Hello from custom task'
outputFile = layout.buildDirectory.file('output.txt')
}
abstract 关键字声明属性,Gradle 会帮你生成实现。别问我为什么,这是 Gradle 7.0+ 的推荐做法。我曾经用普通属性踩过坑,结果增量构建一直不生效。
11.2 自定义 Plugin 开发
Task 写多了,你会发现很多 Task 是成组出现的。比如「打渠道包」需要先清理、再编译、再签名、再对齐。这时候 Plugin 就派上用场了。Plugin 可以把一组 Task 和配置打包成一个可复用的单元。
11.2.1 用 buildSrc 实现
这是最轻量的方式。在项目根目录创建 buildSrc 目录,里面放一个 build.gradle.kts 和源码。所有模块都能自动引用。
// buildSrc/build.gradle.kts
plugins {
`java-gradle-plugin`
}
// buildSrc/src/main/java/com/example/MyPlugin.java
public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("myPluginTask", task -> {
task.doLast(s -> System.out.println("来自 Plugin 的问候"));
});
}
}
然后在 app 模块直接执行 ./gradlew myPluginTask 就能看到效果。我个人习惯把公共的构建逻辑放在 buildSrc 里,比如签名配置、版本号管理。这样所有模块都能共享,而且编译速度比独立工程快。
11.2.2 独立工程发布
当 Plugin 需要跨项目复用,或者需要发布到内部仓库时,独立工程是更好的选择。结构大概是这样的:
my-plugin/
├── build.gradle.kts
├── src/main/
│ ├── java/
│ │ └── com/example/MyPlugin.java
│ └── resources/
│ └── META-INF/gradle-plugins/
│ └── com.example.myplugin.properties
properties 文件内容:
implementation-class=com.example.MyPlugin
发布到 maven 仓库后,其他项目只需要:
plugins {
id("com.example.myplugin") version "1.0.0"
}
我记得有一次,公司内部多个项目都需要做「资源混淆」功能。我把它做成独立 Plugin 发布到私有 Nexus 上,每个项目只需一行配置。嗯,那种感觉确实很爽。
11.3 Extension 扩展配置
Plugin 写好了,但用户怎么传参数?比如渠道名、版本号、是否开启混淆。这时候就需要 Extension 了。Extension 说白了就是给 Plugin 提供一个配置块,让用户在 build.gradle 里写 DSL。
// 定义 Extension 类
abstract class MyExtension {
abstract Property<String> getChannel()
abstract Property<Boolean> getEnableObfuscation()
}
// 在 Plugin 中注册
public class MyPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
MyExtension extension = project.getExtensions()
.create("myConfig", MyExtension.class);
project.getTasks().register("printConfig", task -> {
task.doLast(s -> {
System.out.println("渠道: " + extension.getChannel().get());
System.out.println("混淆: " + extension.getEnableObfuscation().get());
});
});
}
}
用户在 build.gradle 里这样用:
myConfig {
channel = "huawei"
enableObfuscation = true
}
11.4 知识体系总览
下面这张图概括了本章的核心脉络。你可以看到 Task 是基础,Plugin 是组织单元,Extension 是配置入口。三者配合,才能写出优雅的构建逻辑。
11.5 避坑与最佳实践
- 不要滥用 doLast:我曾经在一个项目里看到 200 多行 doLast 嵌套,维护起来简直噩梦。能用 type 就用 type,能抽象成 Task 类就抽象。
- Plugin 的 apply 方法不要写太重逻辑:它只负责注册 Task 和 Extension。真正的执行逻辑放在 Task 的 @TaskAction 里。
- Extension 属性尽量用 Property 类型:这样 Gradle 可以帮你做延迟计算和配置避免。用普通 String 的话,配置顺序一乱就出 bug。
- 独立 Plugin 记得加 group 和 version:不然发布到仓库后,别人根本找不到你的 artifact。
project.afterEvaluate 包裹。
好了,这一章的内容就到这里。Task、Plugin、Extension 这三板斧用好了,你的构建脚本会变得既清晰又强大。下次再遇到重复的构建逻辑,别犹豫,直接上 Plugin。
公众号:蓝海资料掘金营,微信deep3321