上一篇文章【车载Android】使用自定义插件实现多语言自动化适配介绍了自定义插件的使用方式,本文则聚焦"造轮子"------详细讲解如何从头开发一个Android Gradle Plugin,以及如何将其发布到Gradle Plugin仓库,实现开源共享。
源码是最好的老师,本文源码:github.com/linxu-link/...
示例的插件源码和脚本均使用
kotlin语言。
一、创建插件
在Android工程中编写自定义插件主要有两种方式,分为独立插件和私有插件。
独立插件、私有插件是为了方便编写文章时进行分类,临时取得名字,官方文档中并没有相关的概念。
1. 独立插件
独立插件一般可以按照以下步骤进行:
(1)创建插件Module
在Android Studio中,点击"File"->"New"->"New Module"。选择"Android Library"作为模块类型,输入插件模块的名称,如"plugin"。
(2)删除模块内容并创建目录
将新建模块中的内容删除,只保留build.gradle文件和src/main目录。在main目录下新建kotlin目录。
(3)配置 buildSrc/build.gradle.kts
            
            
              scss
              
              
            
          
          plugins {
    `java-gradle-plugin` // 核心插件,提供Gradle插件开发支持
    kotlin("jvm") version "1.9.0"
}
repositories {
 maven(url = "https://maven.aliyun.com/repository/central")
    maven(url = "https://maven.aliyun.com/repository/google")
    google()
    mavenCentral()
}
kotlin {
jvmToolchain(17)
}
dependencies {
 implementation(gradleApi())
    implementation(localGroovy())
    implementation(kotlin("stdlib-jdk8"))
} 更完整代码请参考:github.com/linxu-link/...
(3)创建插件类
在java目录下创建包名,如"com.xx.plugin",来新建名为"XXXPlugin"的kotlin文件。让该类实现org.gradle.api.Plugin接口,示例代码如下:
            
            
              csharp
              
              
            
          
          public class MultilingualPlugin implements Plugin<Project> {
    void apply(Project project) {
        System.out.println("========================");
        System.out.println("hello gradle plugin!");
        System.out.println("========================");
    }
}完整的plugin插件目录如图所示:

2. 私有插件
私有插件一般可以按照以下步骤进行:
(1)创建 buildSrc 目录
在项目根目录下手动创建如下结构(与app、module同级),其余部分与独立插件完全一致:
            
            
              bash
              
              
            
          
          项目根目录/
├── app/
├── module1/
├── buildSrc/               # 核心目录(Gradle自动识别)
│   ├── src/
│   │   └── main/
│   │       ├── kotlin/     # 推荐用Kotlin(或groovy/、java/)
│   │       └── resources/  # 存放插件描述文件等资源
│   └── build.gradle.kts    # buildSrc的构建配置(2)配置插件描述文件
在main目录下新建resources目录,然后在resources目录里面再新建META-INF目录,接着在META-INF里面新建gradle-plugins目录。最后在gradle-plugins目录里面新建properties文件,如"MultilingualPlugin.properties",并在文件中指明自定义的类。
            
            
              ini
              
              
            
          
          implementation-class=com.wj.plugin.multilingual.MultilingualPlugin
version=0.2.0完整的plugin插件目录如图所示:

在Android Studio工程中,buildSrc 是一个特殊的目录/模块 ,用于存放项目构建过程中需要用到的自定义代码(如Gradle插件、任务、工具类等)。它是Gradle原生支持的一个"约定目录"------只要在项目根目录下创建名为buildSrc的文件夹,Gradle就会自动将其识别为构建脚本的源码目录,并在构建时优先编译执行其中的代码。
如果你的自定义插件仅用于当前项目(不打算开源或共享给其他项目),buildSrc 是最佳选择(无需发布到仓库,工程内可以直接使用)。
buildSrc除了用于插件开发,还有以下使用场景:
- 
构建工具类:例如封装通用的文件操作、版本号计算、多模块依赖检查等工具方法。 
- 
统一配置抽取 :例如将多模块共用的 compileSdkVersion、依赖版本号等定义在buildSrc中,实现"一处修改,全局生效"。
3. 私有插件与独立插件的区别
| 特性 | buildSrc | 独立插件 | 
|---|---|---|
| 适用范围 | 仅当前项目内部使用 | 可发布到仓库,供多个项目共享 | 
| 依赖方式 | 自动集成,无需手动依赖 | 需通过 implementation添加依赖 | 
| 发布需求 | 无需发布,随项目源码一起维护 | 需发布到Maven/Gradle仓库 | 
| 适用场景 | 项目私有构建逻辑 | 通用插件(如开源插件、公司内部共享插件) | 
二、Plugin 核心API
在Android Studio中开发Gradle插件时,核心是基于Gradle的API进行扩展。以下是开发中最常用的类、接口和API,我们将逐一讲解。
- 核心入口 :Plugin<Project>接口(apply方法)
- 项目交互 :Project类(任务、配置、文件操作)
- 用户配置 :ExtensionContainer+ 自定义扩展类
- 任务定义 :DefaultTask类 + 注解(@TaskAction、@Input等)
- 文件操作 :RegularFileProperty、DirectoryProperty(支持增量构建)
- 生命周期 :Gradle类 + 监听接口
- 日志输出 :Logger
1. 核心入口:Plugin<Project>
所有Gradle插件的入口,必须实现此接口,通过apply方法来实现插件的各种逻辑。
            
            
              kotlin
              
              
            
          
          import org.gradle.api.Plugin
import org.gradle.api.Project
// 自定义插件主类
class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        // 插件逻辑:注册任务、创建扩展配置等
        project.logger.lifecycle("MyPlugin 已应用到项目:${project.name}")
    }
}- fun apply(project: Project):插件被应用时调用的核心方法,接收- Project实例(代表当前项目)。
2. Project 类
代表一个Gradle项目(模块或根项目),是插件与项目交互的核心入口,提供任务管理、配置、文件操作等能力。
常用方法:
| 方法/属性 | 作用 | 
|---|---|
| tasks | 获取任务容器( TaskContainer),用于注册/管理任务 | 
| extensions | 获取扩展容器( ExtensionContainer),用于创建自定义配置 | 
| projectDir | 当前项目目录( File) | 
| buildDir | 构建输出目录( File,默认projectDir/build) | 
| logger | 日志工具( Logger),用于输出日志 | 
| dependencies | 依赖处理器( DependencyHandler),用于添加依赖 | 
| file(path: String) | 解析项目相对路径为 File对象 | 
| afterEvaluate(action: (Project) -> Unit) | 项目配置完成后执行的回调 | 
示例:
            
            
              kotlin
              
              
            
          
          override fun apply(project: Project) {
    // 1. 获取项目信息
    println("项目名称:${project.name}")
    println("项目路径:${project.projectDir.absolutePath}")
    // 2. 注册任务
    project.tasks.register("myTask") {
        it.group = "Custom"
        it.description = "自定义任务示例"
        it.doLast {
            println("执行我的任务")
        }
    }
    // 3. 项目配置完成后执行
    project.afterEvaluate {
        println("项目配置已完成,任务数量:${it.tasks.size}")
    }
    // 4. 添加依赖(示例:为"implementation"配置添加依赖)
    project.dependencies.add("implementation", "com.google.guava:guava:31.1-jre")
}3. 扩展配置(Extension)
扩展配置允许用户在build.gradle.kts中自定义插件参数(如android { ... }配置块)
例如:MultilingualPlugin支持在build.gradle中使用一些自定义配置项目,这些配置项就需要自定义拓展配置。
            
            
              kotlin
              
              
            
          
          multilingual {
    // 启用多语言适配,默认关闭
    enable.set(true)
    // 使用project.rootDir获取项目根目录,再拼接相对路径
    excelFilePath.set(file("${project.rootDir}/language/多语言V1.0.xlsx").absolutePath)
    // 基准语言目录,必须与代码中资源文件目录一致
    baselineDir.set("values")
    // 基准语言编码,必须与Excel文件中的语言编码一致
    defaultLanguage.set("zh-rCN")
}常用类/方法:
- 
ExtensionContainer:通过project.extensions获取,用于创建和管理扩展。
- 
create(name: String, type: Class<T>, vararg args: Any):创建扩展配置对象。
示例:定义扩展配置
            
            
              vbnet
              
              
            
          
          open class MultilingualExtension(objectFactory: Project) {
    // 插件是否启用
    val enable: Property<Boolean>
    // Excel文件路径配置
    val excelFilePath: Property<String>
    // 默认语言配置
    val defaultLanguage: Property<String>
    // 基线目录配置
    val baselineDir: Property<String>
    init {
        val objects = objectFactory.objects
        excelFilePath = objects.property(String::class.java).convention("")
        defaultLanguage = objects.property(String::class.java).convention("")
        baselineDir = objects.property(String::class.java).convention("values")
        enable = objects.property(Boolean::class.java).convention(false)
    }
}然后在Plugin<Project>的实现类中创建配置:
            
            
              kotlin
              
              
            
          
          class MultilingualPlugin : Plugin<Project> {
    override fun apply(project: Project) {
      // 创建根项目级别的扩展配置
      project.extensions.create("multilingual",MultilingualExtension::class.java)
    }
}然后用户就可以在build.gradle.kts中使用multilingual进行配置。
4.任务(Task)相关类
任务是Gradle执行的最小单元,自定义任务需继承DefaultTask,并通过注解标记输入输出和执行逻辑。
- DefaultTask类
自定义任务的基类,提供任务基础能力。
- 核心注解:
- @TaskAction:标记任务的执行方法(任务被调用时执行)。
- @Input:标记输入参数(用于增量构建,输入变化时任务会重新执行)。
- @OutputFile/- @OutputDirectory:标记输出文件/目录(用于增量构建,输出变化时任务会重新执行)。
- @Optional:标记参数为可选(可不为其赋值)。
- TaskContainer接口
通过project.tasks获取,用于注册、查询、管理任务。
常用方法:
- register(name: String, type: Class<T>, configuration: T.() -> Unit):注册任务(推荐,支持延迟配置)。
- named(name: String):根据名称获取已注册的任务(返回- Provider<Task>)。
- findByName(name: String):根据名称查找任务(返回- Task?)。
示例:自定义任务
            
            
              less
              
              
            
          
          open class MultilingualTask : DefaultTask() {
    @get:Input // excel 文件路径
    val excelFilePath: Property<String> = project.objects.property(String::class.java)
    @get:Input // 默认语言
    val defaultLanguage: Property<String> = project.objects.property(String::class.java)
    @get:Input // 基线文件
    val baselineDir: Property<String> = project.objects.property(String::class.java)
    @TaskAction
    fun generateTranslations() {
    
    }
}然后在Plugin<Project>的实现类中进行注册:
            
            
              kotlin
              
              
            
          
          class MultilingualPlugin : Plugin<Project> {
    override fun apply(project: Project) {
         // ....省略其它方法。
         
        // 注册生成翻译的任务
        project.tasks.register("generateTranslations", MultilingualTask::class.java){             task ->
            // 从根配置获取参数
            task.excelFilePath.set(rootExtension.excelFilePath)
            task.defaultLanguage.set(rootExtension.defaultLanguage)
            task.baselineDir.set(rootExtension.baselineDir)
        }
      
    }
}5. 文件操作相关API
Gradle提供了类型安全的文件操作API,支持增量构建和路径管理,优于直接使用java.io.File。
常用类:
- RegularFileProperty:用于表示单个文件(支持增量构建,推荐替代- File)。
- DirectoryProperty:用于表示目录(支持增量构建)。
- ProjectLayout:通过- project.layout获取,提供项目目录的类型安全访问。
示例:
            
            
              less
              
              
            
          
          import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
abstract class FileProcessorTask : DefaultTask() {
    // 输入目录(通过ProjectLayout获取)
    @get:InputDirectory
    abstract val inputDir: DirectoryProperty
    // 输出文件
    @get:OutputFile
    abstract val outputFile: RegularFileProperty
    @TaskAction
    fun process() {
        val files = inputDir.get().asFile.listFiles()?.toList() ?: emptyList()
        val content = "目录下文件数量:${files.size}\n文件列表:${files.joinToString { it.name }}"
        outputFile.get().asFile.writeText(content)
    }
}
// 在插件中配置路径
override fun apply(project: Project) {
    project.tasks.register<FileProcessorTask>("processFiles") {
        // 使用ProjectLayout获取标准目录(如src/main/resources)
        it.inputDir.set(project.layout.projectDirectory.dir("src/main/resources"))
        // 输出到build目录下的processed.txt
        it.outputFile.set(project.layout.buildDirectory.file("processed.txt"))
    }
}6.依赖管理相关类
通过依赖管理API可动态添加、修改项目依赖。
常用类:
- DependencyHandler:通过- project.dependencies获取,用于添加依赖。
- Configuration:依赖配置(如- implementation、- testImplementation),通过- project.configurations获取。
示例:动态添加依赖
            
            
              kotlin
              
              
            
          
          override fun apply(project: Project) {
    // 为"implementation"配置添加依赖
    project.dependencies.add("implementation", "androidx.core:core-ktx:1.12.0")
    // 为"testImplementation"添加测试依赖
    project.dependencies.add("testImplementation", "junit:junit:4.13.2")
    // 动态创建自定义配置并添加依赖
    val customConfig = project.configurations.create("customLibs")
    project.dependencies.add("customLibs", "com.squareup.okhttp3:okhttp:4.11.0")
}7. 生命周期监听相关类
通过Gradle生命周期API可在构建的特定阶段插入逻辑(如构建开始/结束、任务执行前后)。
常用类:
- Gradle:通过- project.gradle获取,代表整个构建过程。
- TaskExecutionListener:监听任务执行事件。
示例:监听构建生命周期
            
            
              kotlin
              
              
            
          
          override fun apply(project: Project) {
    // 1. 构建开始前执行
    project.gradle.buildStarted {
        project.logger.lifecycle("构建开始!")
    }
    // 2. 所有任务执行完成后执行
    project.gradle.buildFinished { result ->
        project.logger.lifecycle("构建结束!结果:${if (result.success) "成功" else "失败"}")
    }
    // 3. 监听单个任务的执行
    project.tasks.named("generateReport").configure { task ->
        task.doFirst {
            logger.lifecycle("generateReport 任务开始执行...")
        }
        task.doLast {
            logger.lifecycle("generateReport 任务执行完成!")
        }
    }
}8. 日志输出:Logger
用于输出构建日志,支持不同级别(比println更规范,可控制日志可见性)。
常用方法:
- lifecycle(message: String):生命周期级日志(默认可见)。
- info(message: String):信息级日志(需- --info参数可见)。
- debug(message: String):调试级日志(需- --debug参数可见)。
- warn(message: String):警告级日志。
- error(message: String):错误级日志。
示例:
            
            
              kotlin
              
              
            
          
          override fun apply(project: Project) {
    project.logger.lifecycle("===== 生命周期日志 =====")
    project.logger.warn("这是一个警告日志")
    project.logger.debug("这是调试日志(需--debug可见)")
}三、上传 Plugin
将编写好的Gradle插件上传到 plugins.gradle.org(Gradle官方插件仓库),需要完成配置插件的基础信息、准备发布环境、执行发布命令等步骤。
1. 准备工作
(1)注册Gradle账号
访问 Gradle Plugin Portal,使用GitHub账号登录(推荐),完成账号验证。

(2)获取发布密钥
登录后,进入 个人设置页面,生成 API key 和 API secret(用于发布认证,仅显示一次,需保存)。

(3)确保插件符合规范
- 
插件ID需唯一(格式: [命名空间].[用户名].[插件名],如io.github.username.pluginname)。
- 
必须包含文档(至少有 README.md说明使用方法)。
- 
源码需开源(如托管在GitHub)。 
2. 配置插件项目
在插件模块的 build.gradle中添加发布配置
            
            
              csharp
              
              
            
          
          plugins {
    `java-gradle-plugin`// 核心插件,提供插件开发支持
    `maven-publish`// 支持Maven格式发布
    id("com.gradle.plugin-publish") version "1.3.1" // 发布到Gradle Plugin Portal的专用插件
    kotlin("jvm") // 若用Kotlin编写插件
}
// 1. 插件元信息(必填)
group = "io.github.linxu-link" // 命名空间,直接用GitHub用户名
version = "0.2.0"
val pluginDescription = "A plugin that automatically generates Android multi-language resources based on Excel." // 插件描述
// 2. 插件核心配置
gradlePlugin {
    website.set("https://github.com/linxu-link/MultilingualPlugin") // 插件主页(GitHub仓库地址)
    vcsUrl.set("https://github.com/linxu-link/MultilingualPlugin") // 版本控制地址
    plugins {
        register("multilingualPlugin") { // 插件名称(自定义)
            id = "io.github.linxu-link.multilingual" // 插件唯一ID(必须全仓库唯一)
            implementationClass = "com.wj.plugin.multilingual.MultilingualPlugin" // 插件主类
            displayName = "Android Multilingual Plugin"
            description = pluginDescription
            tags.set(listOf("android", "translation", "excel", "localization")) // 用于搜索的标签
        }
    }
}
// 配置 Maven 格式的发布信息
publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
            groupId = project.group.toString()
            artifactId = "multilingual-plugin"
            version = project.version.toString()
        }
    }
}
// 生成源码和文档JAR(提升可用性,推荐)
java {
    withSourcesJar() // 源码JAR(方便用户调试)
    withJavadocJar() // 文档JAR(生成API文档)
}
kotlin {
    jvmToolchain(17)
}
// 依赖配置(根据插件功能添加)
dependencies {
    implementation(gradleApi()) // Gradle API依赖
    implementation(localGroovy())
    implementation(kotlin("stdlib-jdk8")) // kotlin 依赖
}3.执行发布命令
(1)配置密钥
在项目根目录的 gradle.properties 中添加发布密钥(一定不要提交到代码仓库):
            
            
              ini
              
              
            
          
          gradle.publish.key=你的API key
gradle.publish.secret=你的API secret(2)清理并发布
在终端执行以下命令:
            
            
              bash
              
              
            
          
          # 清理构建缓存
./gradlew clean
# 发布插件
./gradlew publishPlugins4. 验证发布
首次发布需人工审核(1-2个工作日),审核通过后,会收到Gradle Plugin官方发送的邮件。后续版本更新会自动审核。

然后在plugins.gradle.org可以看到插件详情中已经给出了集成的方式。

5.常见问题
(1)命名空间不一致被驳回

确保 group 与插件 id 的前缀一致(如 group=io.github.yourusername,id=io.github.yourusername.myplugin),并且username要和github上的username保持一致。
(2)缺失文档被驳回
需在GitHub仓库根目录添加 README.md,详细说明插件功能、集成步骤和使用示例。
