4、插件开发基础:什么是Gradle插件、插件生命周期、如何创建自定义插件

讲到插件开发,我先说说个人理解。Gradle插件说白了就是一段代码,能往构建过程里插一脚——加任务、改配置、挂hook。做我们这个帧率监控插件,你总不能每次都在build.gradle里手写那一堆逻辑吧?把它封装成插件,交给团队用,这是最正经的做法。

我记得最早接触Gradle插件的时候,以为它多神秘。后来发现,就三个核心点:是什么、怎么生、怎么用。下面咱们挨个说。

1. 什么是Gradle插件

Gradle插件是一个实现了 org.gradle.api.Plugin 接口的类。它告诉Gradle:「嘿,我这里有一些扩展和任务,你帮我把它们挂到项目上。」

  • 脚本插件:写一个 .gradle 或 .gradle.kts 文件,用 apply from 引入——简单,但不好复用。
  • buildSrc 插件:丢在项目根目录的 buildSrc/src/main/java 下,多项目共享很方便。
  • 独立插件:打成jar包发布到仓库,团队内所有项目都能引用——我们的监控插件最终就做成这种形式。
类型作用域适合场景
脚本插件单个项目临时封装几行逻辑
buildSrc单项目多模块内部共享,无需发布
独立插件全局/团队正式发布、版本管理

关键点:我们的监控插件属于独立插件。因为需要打版本、上传maven,让不同APP项目都能依赖。

2. 插件生命周期——3个阶段

Gradle构建过程分成初始化、配置、执行三个阶段。插件参与的是后两个。

  • 初始化:Gradle扫描settings文件,决定哪些项目参与构建。插件这时候还没加载。
  • 配置:所有 build.gradle 里的代码都跑一遍——包括插件的 apply() 方法、注册Extension、给任务设默认值。注意!这阶段只是定义,任务不会真的执行。
  • 执行:只有被你显式调用的任务才会干活。监控插件的采集任务、报告生成任务等,都是在执行阶段才跑起来的。

为什么要分清楚?我在项目中遇到过一个大坑:曾经在配置阶段放了网络请求去拉配置,结果每次执行 gradle tasks 都等半天。其实配置阶段应该尽量轻量,只做描述性工作。

避坑指南:我建议配置阶段不要做任何IO、网络或者耗时计算。只注册Extension的默认值、声明Task之间的依赖关系就好。

Gradle 插件生命周期 阶段1:初始化 设置文件扫描、Project创建 插件尚未加载 阶段2:配置 apply() 注册 Extension 定义任务,但不执行 阶段3:执行 Task 真正运行 采集、输出报告 关键扩展点: project.afterEvaluate / taskGraph.whenReady ⚠️ 配置阶段别放耗时逻辑,否则每次 gradle 任务都拖慢

3. 如何创建一个自定义插件

好,现在咱们手写一个极简的监控插件骨架。代码我贴在下面,你顺着思路走就行。

// 1. 实现 Plugin 接口
class FpsMonitorPlugin : Plugin {
    override fun apply(project: Project) {
        // 2. 注册 Extension,让用户可配置
        val extension = project.extensions.create("fpsMonitor", FpsMonitorExtension::class.java)

        // 3. 创建 Task
        project.tasks.register("collectFps", CollectFpsTask::class.java) { task ->
            task.group = "fps"
            task.description = "采集UI帧率数据"
            // 任务依赖 Extension 的配置值
            task.outputDir.set(extension.outputDir)
        }

        // 4. (可选)afterEvaluate 确保所有配置生效后再做初始化
        project.afterEvaluate {
            // 可以做验证:检查必要的配置是否为空
            if (extension.outputDir.getOrNull() == null) {
                throw GradleException("fpsMonitor.outputDir 必须配置")
            }
        }
    }
}

// 2. Extension 类,接收 DSL 配置
open class FpsMonitorExtension {
    val outputDir: Property<String> = objects.property(String::class.java)
    val reportFormat: Property<String> = objects.property(String::class.java).convention("html")
}

这里面需要注意几件事:

  • project.extensions.create("fpsMonitor", ...) 这行决定了用户在 build.gradle 里怎么写配置块:fpsMonitor { outputDir = "build/reports" }
  • Task 的注册 要放在 register 而不是 create,这样只有被依赖或显式调用时才实例化,节省配置时间。
  • 我在项目里习惯把 afterEvaluate 只做验证用,别又往里加新的Task逻辑——否则容易跟其他插件打架。

小技巧:用 objects.property(String::class.java) 代替 var,这样能享受延迟赋值和默认值好处,也方便在 afterEvaluate 时做校验。

4. 插件的发布与使用

写好了插件怎么给团队用?按以下步骤走:

  1. 把插件源码放到 src/main/java 下,加上 resources/META-INF/gradle-plugins/插件名.properties 文件,内容指定实现类。
  2. 用 java-gradle-plugin + maven-publish 插件发布到公司的Maven仓库。
  3. 使用方在 build.gradle 里添加仓库地址和 id 'com.example.fps-monitor'。
// 发布配置示例(plugins 块内)
plugins {
    `java-gradle-plugin`
    `maven-publish`
}

gradlePlugin {
    plugins {
        register("fpsMonitor") {
            id = "com.example.fps-monitor"
            implementationClass = "com.example.FpsMonitorPlugin"
        }
    }
}

publishing {
    repositories {
        maven { url = uri("https://your.company.com/repo") }
    }
}

有同学喜欢用 buildSrc 直接内嵌,这个看团队规模。如果超过两个项目复用,我个人建议还是走发布路线——版本管理、变更日志、回滚都方便。

5. 你想想看,这个生命周期能用来做什么?

拿我们的帧率监控来说:

  • 配置阶段:注册 fpsMonitor Extension,用户设置输出目录、采样时长。
  • 配置阶段末尾(afterEvaluate):检查必要参数,缺了就报错,及早提醒。
  • 执行阶段:collectFps 任务启动Instrumentation Test,收集每一帧的时间戳;generateReport 任务把原始数据转成图表。

你可能会问:为什么不在配置阶段直接跑测试?因为Gradle的配置阶段可能被 gradle tasks 或 gradle projects 触发,这时候跑Instrumentation Test就乱套了。所以老老实实注册Task,只在实际需要时才执行。

一句话总结:插件就是把分散的构建逻辑按「配置 → 执行」分阶段封装好,让团队像用乐高一样拼出构建流程。下次你看到别人的Gradle插件,心里应该能拆出「Extension做了什么」「哪些Task」「生命周期里在哪个阶段注册的」这几块了。