自定义插件:Plugin接口、apply()方法、插件ID与发布、buildSrc与复合构建、插件最佳实践

说实话,Gradle脚本写多了,你会发现一个尴尬的事——同样的配置逻辑,你在每个module里都要复制粘贴一遍。比如统一处理所有module的lint配置、统一添加某个第三方服务的初始化代码。这时候,你就需要自定义插件了。

我刚开始接触自定义插件时,觉得这东西挺玄乎的。后来发现,说白了就是把一段可复用的构建逻辑封装起来,打个包,到处用。今天我们就把它彻底聊透。

Plugin接口与apply()方法

自定义插件的核心,就是实现Plugin<Project>接口。这个接口只有一个方法:apply()。当插件被应用到项目上时,Gradle会调用这个方法,并把Project对象传给你。

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        // 在这里写你的构建逻辑
        project.tasks.register("hello") {
            doLast {
                println("Hello from MyPlugin!")
            }
        }
    }
}

嗯,就这么简单。你可以在apply()里做任何事:注册任务、添加扩展、配置依赖、修改已有任务的逻辑。我记得第一次写插件时,就犯了个低级错误——在apply()里直接执行耗时操作。后来发现,插件应用时项目还没完全配置好,很多属性是空的。所以,尽量在apply()里只做注册和配置,不要做执行

注意:不要在apply()方法中直接执行文件读写、网络请求等操作。这些操作应该放在任务(Task)的doLast或doFirst中执行。

插件ID与发布

插件ID就像你的身份证。Gradle通过它来唯一标识一个插件。命名规则通常是反向域名,比如com.example.myplugin

定义插件ID有两种方式:

  • 使用build.gradle.kts中的属性:在插件模块的build.gradle.kts中声明
  • 使用META-INF描述文件:在src/main/resources/META-INF/gradle-plugins/下创建插件ID.properties文件

我个人更推荐第二种方式,因为它更清晰,也方便发布到仓库。

// 文件路径: src/main/resources/META-INF/gradle-plugins/com.example.myplugin.properties
implementation-class=com.example.MyPlugin

发布插件到本地仓库或远程仓库,你需要配置maven-publish插件:

plugins {
    `java-gradle-plugin`
    `maven-publish`
}

gradlePlugin {
    plugins {
        register("myPlugin") {
            id = "com.example.myplugin"
            implementationClass = "com.example.MyPlugin"
        }
    }
}

publishing {
    repositories {
        maven {
            url = uri("../local-repo")
        }
    }
}

发布后,其他项目就可以通过plugins { id("com.example.myplugin") }来引用了。我曾经踩过一个坑:发布时忘记更新版本号,结果同事拉下来还是旧版本,排查了半天。所以,版本管理一定要自动化,别靠手动改。

buildSrc与复合构建

如果你的插件只在一个项目里用,没必要发布到仓库。Gradle提供了两种本地方案:buildSrc和复合构建。

buildSrc

buildSrc是一个特殊的目录,放在项目根目录下。Gradle会自动编译它,并把它加入到所有module的classpath中。你只需要在buildSrc/src/main/kotlin下写插件代码,然后在任意module的build.gradle.kts中直接使用。

// buildSrc/src/main/kotlin/MyPlugin.kt
class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("myTask") {
            doLast { println("buildSrc plugin works!") }
        }
    }
}

// app/build.gradle.kts
plugins {
    id("com.android.application")
    // 直接使用,不需要apply plugin
}

apply<MyPlugin>()

buildSrc的好处是简单,但有个缺点:任何改动都会导致整个项目重新构建。我有个项目用了buildSrc,每次改一行代码,全项目都要重新编译,那叫一个酸爽。所以,如果你的插件逻辑比较复杂,或者团队比较大,我建议用复合构建。

复合构建

复合构建(Composite Build)允许你把一个独立的Gradle项目作为另一个项目的依赖。你需要创建一个独立的插件项目,然后在主项目的settings.gradle.kts中引用它。

// settings.gradle.kts
includeBuild("../my-plugin-project")

复合构建的好处是:插件项目可以独立编译,不会影响主项目的构建缓存。而且,你可以同时开发插件和主项目,调试起来非常方便。

我的建议:小团队或简单逻辑用buildSrc,大团队或复杂逻辑用复合构建。别问我怎么知道的,都是泪。

插件最佳实践

写插件和写业务代码一样,也有套路。我总结了几个关键点:

  1. 提供扩展(Extension):让用户可以通过DSL配置你的插件,而不是硬编码。
  2. 延迟配置:使用afterEvaluateproject.provider,确保所有配置都就绪后再执行。
  3. 避免全局状态:插件可能被应用到多个project,不要用静态变量保存状态。
  4. 处理多变体(Variants):如果是Android插件,要考虑不同buildType、flavor的情况。
  5. 提供清晰的错误信息:用户配置错了,要给出明确的提示,而不是抛个NullPointerException。

举个例子,如何提供一个扩展:

open class MyExtension {
    var message: String = "default"
    var enabled: Boolean = true
}

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val extension = project.extensions.create("myConfig", MyExtension::class.java)
        project.tasks.register("printMessage") {
            doLast {
                if (extension.enabled) {
                    println(extension.message)
                }
            }
        }
    }
}

// 用户使用
myConfig {
    message = "Hello from user!"
    enabled = true
}

你看,这样用户就可以通过myConfig { ... }来配置了,体验好很多。

核心原则:插件是给其他开发者用的,要像写API一样写插件。易用性、稳定性、可调试性,一个都不能少。

知识体系图

下面这张图帮你理清自定义插件的核心脉络:

自定义插件知识体系 Plugin<Project> 实现 apply() 方法 插件ID与发布 buildSrc / 复合构建 注册Task / 添加Extension META-INF / gradlePlugin {} maven-publish 发布 buildSrc 自动编译 includeBuild 复合构建 最佳实践:扩展配置 / 延迟执行 / 避免全局状态

这张图把自定义插件的三个核心维度串起来了:怎么实现、怎么注册、怎么在本地用。你写插件时,沿着这个思路走,基本不会跑偏。

好了,关于自定义插件的内容就聊到这里。记住,插件是构建脚本的终极抽象,用好它,你的构建脚本会变得干净、可维护、可复用。下次再遇到重复的配置,别复制粘贴了,写个插件吧。


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