插件开发实战:四大核心场景
说实话,插件开发这个话题,很多同学觉得门槛高。其实不然。
我刚开始接触 Gradle 插件时,也觉得这东西很神秘。后来做多了才发现,插件说白了就是一段能在构建过程中执行的代码。你写个 Task,注册到某个生命周期里,它就活了。
今天咱们聊四个实战场景:组件化插件、自动注册插件、资源合并插件、代码生成插件。这四个场景覆盖了日常开发中 80% 的插件需求。
核心观点:插件不是炫技,是解决重复劳动的工具。你想想看,每次手动注册 Activity、手动合并资源、手动写模板代码,这些事交给插件做,它不香吗?
一、组件化插件:模块化架构的基石
组件化,这个词大家都不陌生。但真正落地时,你会发现每个模块都要写一堆重复的配置:build.gradle 里的依赖、版本号、插件声明……
我个人习惯写一个 ComponentPlugin,把这些重复劳动统一管理。
1.1 核心思路
组件化插件要解决三个问题:
- 依赖统一:所有模块的依赖版本由插件统一管理
- 路由配置:自动注入 ARouter 或 WMRouter 的配置
- 构建变体:区分 debug/release 的组件独立运行模式
我的经验:组件化插件不要写得太重。我见过有人把整个构建逻辑塞进插件里,结果出了问题很难排查。插件只做「配置注入」,具体逻辑交给各个模块的 build.gradle 去处理,这样更灵活。
1.2 代码示例
class ComponentPlugin : Plugin<Project> {
override fun apply(project: Project) {
// 1. 统一依赖版本
project.extensions.create("componentConfig", ComponentExtension::class.java)
// 2. 根据模块类型注入不同配置
project.afterEvaluate {
val isApp = project.plugins.hasPlugin("com.android.application")
if (isApp) {
configureAppModule(project)
} else {
configureLibModule(project)
}
}
}
private fun configureAppModule(project: Project) {
project.android {
// 注入路由配置
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments(mapOf(
"AROUTER_MODULE_NAME" to project.name,
"AROUTER_GENERATE_DOC" to "enable"
))
}
}
}
}
}
}
二、自动注册插件:告别 AndroidManifest 手动配置
嗯,这里要注意。自动注册插件不是让你在运行时动态注册,而是在编译期把 Activity、Service 等信息写入 Manifest 文件。
我曾经在一个大型项目中,有 30 多个模块,每个模块都要手动在 Manifest 里声明 Activity。后来写了个自动注册插件,每次新增 Activity 只需要加个注解,插件自动处理 Manifest 合并。
2.1 实现原理
说白了就是三步:
- 扫描所有类文件,找到带
@AutoRegister注解的类 - 解析注解参数(如
exported、intentFilter) - 生成 Manifest 片段,注入到合并流程中
避坑指南:我曾经在扫描类文件时用了全量扫描,导致构建时间增加了 30 秒。后来改用 ASM 在字节码层面处理,速度提升了很多。记住,插件开发一定要考虑性能。
2.2 关键代码
class AutoRegisterTransform : Transform() {
override fun transform(transformInvocation: TransformInvocation) {
val outputProvider = transformInvocation.outputProvider
transformInvocation.inputs.forEach { input ->
input.jarInputs.forEach { jarInput ->
// 扫描 jar 包中的 class 文件
val jarFile = jarInput.file
val matchedClasses = scanJarForAnnotations(jarFile)
// 生成 Manifest 片段
val manifestFragment = generateManifestFragment(matchedClasses)
writeManifestFragment(manifestFragment, outputProvider)
}
}
}
}
三、资源合并插件:解决多模块资源冲突
资源合并,这个坑我踩过很多次。多个模块定义了同名的 ic_launcher.png 或 colors.xml,合并时要么覆盖,要么报错。
资源合并插件的核心是:在合并之前做预处理。
3.1 合并策略
| 冲突类型 | 默认行为 | 插件处理 |
|---|---|---|
| 同名资源 | 后加载的覆盖前加载的 | 自动添加模块前缀 |
| 相同内容 | 保留一份 | 去重并记录日志 |
| 命名不规范 | 不处理 | 自动重命名 |
我的建议:资源合并插件最好配合 lint 检查一起使用。在 CI 流程中,如果发现资源冲突,插件可以自动修复并提交 PR。这样能减少很多人工 review 的工作量。
四、代码生成插件:消灭模板代码
你想想看,每个新页面都要写 Activity、ViewModel、Layout、Adapter……这些模板代码写多了真的烦。
代码生成插件就是干这个的。它可以在编译期根据你的配置,自动生成这些模板文件。
4.1 生成策略
- 基于模板:预定义 FreeMarker 或 KotlinPoet 模板
- 基于注解:通过注解处理器生成代码
- 基于配置:读取 YAML 或 JSON 配置文件生成
4.2 实战示例
class CodeGeneratorPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("generatePage") {
doLast {
val template = project.rootProject.file("templates/PageTemplate.kt")
val outputDir = project.projectDir.resolve("src/main/java/com/example")
// 读取模板并替换占位符
val content = template.readText()
.replace("\${package}", "com.example")
.replace("\${className}", "MainActivity")
// 写入生成的文件
outputDir.resolve("MainActivity.kt").writeText(content)
}
}
}
}
核心要点:代码生成插件一定要支持「增量生成」。我见过一个插件每次运行都全量生成,结果把开发者手动修改的代码覆盖了。后来我们加了文件比对逻辑,只生成不存在的文件,或者文件内容有差异时才覆盖。
总结
这四个插件场景,覆盖了 Android 构建过程中的大部分痛点。组件化插件解决架构问题,自动注册插件解决配置问题,资源合并插件解决冲突问题,代码生成插件解决效率问题。
我个人觉得,插件开发最难的不是技术,而是「想清楚要解决什么问题」。你写插件之前,先问问自己:这个流程能不能自动化?这个配置能不能统一管理?这个模板能不能自动生成?想清楚了,写起来就顺了。
公众号:蓝海资料掘金营,微信deep3321