1. 为什么需要将Android Gradle插件发布到Maven仓库
在Android开发团队协作中,我们经常会遇到这样的场景:多个项目需要共享同一套构建逻辑或自定义功能。比如公司内部有一套统一的代码规范检查规则,或者封装了特定的资源处理流程。这时候,把相关功能封装成Gradle插件并发布到Maven仓库就成为了最佳实践方案。
我经历过一个典型case:团队有20+个Android应用项目,每个都需要配置相同的ProGuard规则、代码质量检测和CI集成脚本。最初是在每个项目的build.gradle里复制粘贴相同配置,结果每次规则更新都需要人工同步所有项目,漏掉一个就会导致构建结果不一致。后来我们把公共配置抽离成自定义插件并发布到内部Maven仓库,所有项目只需声明插件依赖,版本升级一次搞定。
技术实现上,Gradle插件发布到Maven主要解决三个问题:
- 依赖管理:像使用其他第三方库一样通过
implementation引入插件 - 版本控制:可以像普通库一样通过版本号管理迭代
- 构建复用:避免相同配置代码在多项目间重复
2. 插件开发基础环境搭建
2.1 项目结构规划
一个标准的可发布插件项目通常采用多模块结构(以Android Studio Arctic Fox为例):
code复制plugin-project/
├── build.gradle
├── settings.gradle
├── plugin-core/ // 插件主逻辑模块
│ ├── build.gradle
│ └── src/main/
│ ├── groovy/ // Groovy源码
│ └── resources/
│ └── META-INF.gradle-plugins/
│ └── com.your.plugin.properties
└── plugin-test/ // 测试模块
└── build.gradle
关键点说明:
- Groovy目录用于存放插件主要逻辑代码
- resources/META-INF下的.properties文件是插件的身份证,文件名对应插件ID
2.2 必备依赖配置
在plugin-core的build.gradle中需要声明这些关键依赖:
groovy复制plugins {
id 'groovy' // Groovy语言支持
id 'java-gradle-plugin' // Gradle插件开发支持
id 'maven-publish' // Maven发布能力
}
dependencies {
implementation gradleApi() // Gradle API
implementation localGroovy() // Groovy支持
// 可选:Android相关API(如果是Android插件)
implementation 'com.android.tools.build:gradle:7.2.1'
}
注意:Gradle插件与Android Gradle Plugin(AGP)版本存在兼容性矩阵,比如AGP 7.x需要Gradle 7.x+支持。建议在插件文档中明确声明版本要求。
3. 插件开发核心实现
3.1 插件入口类编写
创建一个Groovy类作为插件入口(示例使用Groovy DSL,也可用Kotlin):
groovy复制package com.your.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyAndroidPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
// 注册扩展配置
def extension = project.extensions.create('myPlugin', PluginExtension)
project.afterEvaluate {
// 配置解析完成后执行
project.android.applicationVariants.all { variant ->
// 为每个构建变体添加自定义任务
variant.outputs.each { output ->
project.tasks.register(
"generate${variant.name.capitalize()}Report",
GenerateReportTask) {
group = 'custom'
description = "Generates build report for ${variant.name}"
outputDir = new File(project.buildDir, "reports/${variant.name}")
versionName = variant.versionName
}
}
}
}
}
}
// 扩展配置类
class PluginExtension {
String customParam = 'default'
boolean enableFeature = true
}
3.2 插件属性文件配置
在resources/META-INF/gradle-plugins目录下创建com.your.plugin.properties文件:
code复制implementation-class=com.your.plugin.MyAndroidPlugin
这个文件名就是插件ID(com.your.plugin),用户apply时会用到。
3.3 自定义任务实现
上面代码中的GenerateReportTask示例实现:
groovy复制package com.your.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class GenerateReportTask extends DefaultTask {
File outputDir
String versionName
@TaskAction
void generate() {
def reportFile = new File(outputDir, "build_report.txt")
reportFile.parentFile.mkdirs()
reportFile.text = """
Build Report
============
Version: $versionName
Date: ${new Date()}
"""
}
}
4. Maven发布配置详解
4.1 本地发布配置
先在plugin-core的build.gradle中添加发布配置:
groovy复制publishing {
publications {
mavenJava(MavenPublication) {
from components.java
// 自定义POM信息
pom {
name = 'My Android Plugin'
description = 'A custom Gradle plugin for Android projects'
url = 'http://example.com'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'dev'
name = 'Your Name'
email = 'dev@example.com'
}
}
}
}
}
repositories {
maven {
// 本地仓库路径
url = uri("${project.buildDir}/repo")
}
}
}
执行发布命令:
bash复制./gradlew publish
发布成功后,在build/repo目录下会生成maven元数据和插件jar包。
4.2 远程仓库发布
以Nexus私服为例,修改repositories配置:
groovy复制repositories {
maven {
url = "http://nexus.example.com/repository/maven-releases/"
credentials {
username = project.findProperty('nexusUsername')
password = project.findProperty('nexusPassword')
}
}
}
安全建议:
- 不要在build.gradle中硬编码账号密码
- 通过gradle.properties或命令行参数传入
- 或者使用环境变量
发布到远程仓库:
bash复制./gradlew publish -PnexusUsername=user -PnexusPassword=pass
5. 插件使用实践
5.1 项目引用已发布插件
在应用项目的settings.gradle中添加仓库配置:
groovy复制pluginManagement {
repositories {
maven {
url = uri("http://nexus.example.com/repository/maven-releases/")
}
gradlePluginPortal()
}
}
在app模块的build.gradle中应用插件:
groovy复制plugins {
id 'com.android.application'
id 'com.your.plugin' version '1.0.0'
}
myPlugin {
customParam = 'production'
enableFeature = false
}
5.2 版本冲突解决策略
当插件依赖的库与项目依赖冲突时,可以在插件build.gradle中强制指定版本:
groovy复制configurations.all {
resolutionStrategy {
force 'com.google.guava:guava:30.1.1-android'
force 'org.apache.commons:commons-lang3:3.12.0'
}
}
也可以在应用项目中通过dependencyResolutionManagement统一管理:
groovy复制dependencyResolutionManagement {
resolutionStrategy {
eachDependency { details ->
if (details.requested.group == 'com.google.guava') {
details.useVersion '30.1.1-android'
}
}
}
}
6. 高级技巧与避坑指南
6.1 增量构建优化
插件任务实现增量构建可以提高构建速度:
groovy复制class OptimizedTask extends DefaultTask {
@Input
String inputParam
@OutputDirectory
File outputDir
@TaskAction
void execute() {
// 只有输入变化时才会执行
}
}
关键注解:
@Input:标记输入参数@OutputFile/@OutputDirectory:标记输出@InputFiles/@InputDirectory:标记输入文件
6.2 常见问题排查
问题1:插件加载失败,报错"Plugin with id 'com.your.plugin' not found"
解决方案:
- 检查settings.gradle中的pluginManagement是否包含正确仓库
- 确认插件版本号是否正确
- 检查插件jar是否已成功发布到仓库
问题2:构建时出现类冲突
解决方案:
- 使用
gradle dependencies查看依赖树 - 在插件中通过
compileOnly代替implementation声明非必要依赖 - 使用
resolutionStrategy.force统一版本
问题3:插件任务没有出现在task列表中
解决方案:
- 确认任务已正确注册
- 检查任务是否设置了
group属性(未设置分组的任务默认隐藏) - 使用
--all参数查看所有任务:./gradlew tasks --all
6.3 性能优化建议
- 延迟配置:在
afterEvaluate中执行耗时操作 - 缓存机制:对重复计算结果使用缓存
- 并行处理:对独立任务启用并行执行
- 构建扫描:使用
--scan参数生成构建分析报告
groovy复制project.gradle.startParameter {
// 启用并行构建
parallelProjectExecutionEnabled = true
// 启用配置缓存
configurationCache = true
}
7. 插件测试策略
7.1 单元测试
使用Gradle TestKit测试插件逻辑:
groovy复制class MyPluginTest extends Specification {
def "plugin applies correctly"() {
given:
def project = ProjectBuilder.builder().build()
when:
project.pluginManager.apply('com.your.plugin')
then:
project.tasks.findByName('generateDebugReport') != null
}
}
7.2 功能测试
创建测试项目验证插件集成:
groovy复制task testPlugin(type: GradleBuild) {
dir = file('test-project')
tasks = ['clean', 'build']
doLast {
if (new File(dir, 'build/reports/debug/build_report.txt').exists()) {
println "Test passed!"
} else {
throw new GradleException("Test failed - report not generated")
}
}
}
7.3 自动化发布流程
结合CI工具实现自动化发布:
yaml复制# GitHub Actions示例
name: Publish Plugin
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '11'
- run: ./gradlew publish
env:
ORG_GRADLE_PROJECT_nexusUsername: ${{ secrets.NEXUS_USER }}
ORG_GRADLE_PROJECT_nexusPassword: ${{ secrets.NEXUS_PASS }}
8. 插件文档与维护
8.1 文档生成
使用Dokka生成API文档:
groovy复制plugins {
id 'org.jetbrains.dokka' version '1.6.10'
}
task dokkaHtml(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputDirectory = file("$buildDir/dokka")
moduleName = 'my-plugin'
}
8.2 版本管理策略
推荐语义化版本控制:
- MAJOR:不兼容的API修改
- MINOR:向下兼容的功能新增
- PATCH:向下兼容的问题修正
在gradle.properties中定义版本:
properties复制pluginVersion=1.0.0
8.3 兼容性矩阵
在README中明确声明:
| 插件版本 | Gradle版本 | Android Gradle插件版本 |
|---|---|---|
| 1.0.x | 7.0+ | 7.0+ |
| 0.9.x | 6.5+ | 4.2+ |
9. 实际案例:资源检查插件
分享一个我们团队实际使用的资源检查插件实现要点:
groovy复制class ResourceCheckPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
def task = project.tasks.register(
"check${variant.name.capitalize()}Resources",
ResourceCheckTask) {
variantName = variant.name
resDir = variant.mergeResourcesProvider.get().outputDir
}
variant.assembleProvider.configure {
it.dependsOn(task)
}
}
}
}
class ResourceCheckTask extends DefaultTask {
@Input
String variantName
@InputDirectory
File resDir
@TaskAction
void check() {
def pattern = ~/^[a-z0-9_]+$/
resDir.eachFileRecurse { file ->
if (file.name.endsWith('.xml')) {
def matcher = (file.name - '.xml') =~ pattern
if (!matcher.matches()) {
logger.error("Invalid resource name: ${file.name}")
throw new GradleException(
"Resource naming violation in ${variantName} build")
}
}
}
}
}
这个插件会在构建时检查所有资源文件名是否符合命名规范(小写字母、数字和下划线),不符合则中断构建。我们在CI流程中集成此插件,有效统一了团队资源命名规范。
