Android Gradle学习 - Gradle插件开发与发布指南

前言

   在上一篇笔记中 Android Gradle 学习 - 生命周期和Task ,汇总了Task的基础知识,这一篇就汇总下 项目中对于插件的使用

Plugin 插件作用

  Gradle Plugin 是一种用于扩展和定制 Gradle构建系统功能的机制。插件可以执行各种任务,包括编译代码、执行测试、打包文件、生成文档等等操作。插件可以访问和操作 Gradle的构建模型,如项目、任务、依赖关系等,实现对构建过程的控制和定制,从而提高开发效率。

  站在工程和团队角度看 Plugin ,有三大优点:

  1. 封装: 把具体逻辑抽出去,降低build.gradle的复杂度。
  2. 复用和统一:通用逻辑可以提供给多个项目使用,统一构建规范,提供标准化配置。
  3. 定制拓展: 可在编译期间插桩、Hook、字节码操作等定制操作。

Plugin 不同创建方式

根据插件不同的作用域,可以细分为三种 Plugin写法

位置 作用域 适用场景
build.gradle 当前Project 简单逻辑、快速验证
buildSrc 当前项目所有Module 项目内共享逻辑
独立项目 所有项目(发布后) 跨项目复用、开源分享

项目结构示意:

less 复制代码
root/
├── build.gradle      //方式1:写在这里
├── buildSrc/         //方式2:写在这里
│   ├── build.gradle.kts
│   └── src/main/kotlin/
│       └── MyPlugin.kt
├── my-plugin/        //方式3:独立模块
│   ├── build.gradle.kts
│   └── src/main/kotlin/
│       └── MyPlugin.kt
└── app/
    └── build.gradle

build.gradle中编写插件

   只在当前文件域中生效,虽然写法简单,但是不能复用,简单测试逻辑还是可以的,这里简单列举下模版代码。

kotlin 复制代码
// 定义插件类
class HelloPlugin : Plugin {
    override fun apply(project: Project) {
        println("Hello Plugin被应用了!")
    }
}

// 应用插件
apply()

Sync后输出

markdown 复制代码
> Configure project :app
Hello Plugin被应用了!

buildSrc中编写插件

buildSrc是Gradle的约定目录,放在项目根目录下:

  • Gradle会自动编译这个目录
  • 编译结果会自动添加到构建脚本的classpath
  • 对项目中所有Module可见
less 复制代码
root/
├── buildSrc/
│   ├── build.gradle.kts         // 构建配置
│   └── src/
│       └── main/
│           ├── kotlin/         
│           │   └── com/
│           │       └── example/
│           │           └── MyPlugin.kt     // 插件
│           └── resources/
│               └── META-INF/
│                   └── gradle-plugins/
│                       └── my-plugin.properties   //插件ID映射
├── app/
│   └── build.gradle.kts
└── settings.gradle.kts

buildSrc中的build.gradle.kts

kotlin 复制代码
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
    google()
}

//如果根布局 build.gradle 中已经依赖了 agp ,这里不需要添加

buildSrc/src 文件下的 MyPlugin.kt

kotlin 复制代码
class MyPlugin : Plugin {

    override fun apply(project: Project) {
        println("MyPlugin 应用到: ${project.name}")

        // 创建扩展
        val extension = project.extensions.create(
            "myConfig",
            MyPluginExtension::class.java
        )
        val taskProvider = project.tasks.register("myTask")
        taskProvider.configure(object : Action {
            override fun execute(task: Task) {
                task.group = "my-plugin"
                task.description = "MyPlugin提供的任务"
                
                task.doLast(object : Action {
                    override fun execute(t: Task) {
                        println("执行myTask")
                        println("配置信息: ${extension.message.get()}")
                    }
                })
            }
        })

        // 项目评估完成后执行
        project.afterEvaluate(object : Action {
            override fun execute(p: Project) {
                println("项目 ${p.name} 配置完成")
            }
        })
    }
}

// 扩展类
abstract class MyPluginExtension {
    abstract val message: Property

    init {
        message.convention("默认消息")
    }
}

my-plugin.properties 插件id映射文件 这样可以使用简短的插件id

properties 复制代码
implementation-class=MyPlugin

使用插件,这里我是放在了 build.gradle 文件中了 (根布局我是用的 groovy格式)

groovy 复制代码
apply plugin: MyPlugin

// 配置扩展
myConfig {
    message = "Hello from app module!"
}

独立模块编写 Plugin

这里先创建一个 Library ,然后我们再将部分配置修改为 Gradle插件

将Library 改造为 插件Module (移除 AndroidManifest.xml和proguard-rules 等混淆文件 修改build.gradle.kts)

创建 MyGradlePlugin.kt

kotlin 复制代码
class MyGradlePlugin : Plugin {

    companion object {
        const val TAG = ">>>>>>> MyGradlePlugin: "
    }

    override fun apply(project: Project) {
        println("$TAG 插件已应用到 ${project.name}")

        // 1. 创建扩展配置
        val extension = project.extensions.create(
            "myPluginConfig",
            MyExtension::class.java
        )

        // 2. 创建 Task
        val taskProvider = project.tasks.register("printProjectInfo")
        taskProvider.configure(object : Action {
            override fun execute(task: Task) {
                task.group = "my-plugin"
                task.description = "打印项目信息"

                task.doLast(object : Action {
                    override fun execute(t: Task) {
                        println("项目名称: ${project.name}")
                        println("项目路径: ${project.projectDir}")
                        println("应用名称: ${extension.appName.getOrElse("未设置")}")
                        println("版本号: ${extension.versionName.getOrElse("未设置")}")
                    }
                })
            }
        })

        // 3. 项目评估完成后执行
        project.afterEvaluate(object : Action {
            override fun execute(p: Project) {
                if (extension.enable.getOrElse(true)) {
                    println("$TAG 插件功能已启用")
                } else {
                    println("$TAG 插件功能已禁用")
                }
            }
        })
    }
}

修改 build.gradle.kts

kotlin 复制代码
plugins {
    `kotlin-dsl`
    `maven-publish`
    `java-gradle-plugin`
}

group = "com.xmly"
version = "1.0.0"

dependencies {
    implementation(gradleApi())
}

// 配置插件信息
gradlePlugin {
    plugins {
        create("myPlugin") {
            id = "com.xmly.myplugin"  // 插件ID
            implementationClass = "com.xmly.plugin.MyGradlePlugin" // 实现类
            displayName = "My Gradle Plugin"
            description = "示例 Gradle 插件"
        }
    }
}

// 发布配置
publishing {
    repositories {
        maven {
            name = "local"
            url = uri("${rootProject.projectDir}/repo")  // 发布到项目根目录的 repo 文件夹
        }
    }
}

这里暂时选本地,就是看看效果,如果有云端 maven地址也可以换一下,然后我们进行打包,因为Library 的插件是不能直接通过 apply 依赖到

发布插件到本地

ruby 复制代码
./gradlew :gradle-plugin:publish

可以查看根目录是否产生repo文件和对应版本jar 判断是否发布成功

在根目录的 setting.gradle 文件中配置 本地地址

gradle 复制代码
pluginManagement {
    repositories {
        maven { url './repo' } 
        // ... 其他仓库
    }
}

然后在 build.gradle.kts 中添加对应插件依赖

kotlin 复制代码
plugins {
    ... //省略自己引入的插件
    id("com.xmly.myplugin") version "1.0.0"
}

myPluginConfig {
    enable.set(true)
    appName.set("MyApp")
}

现在sync 我们就可以看到 MyGradlePlugin: 插件功能已启用 的字样 在命令行中执行 ./gradlew :app:printProjectInfo 即可看到

gradle 复制代码
> Task :app:printProjectInfo
  [执行] 开始::app:printProjectInfo
项目名称: app
项目路径: /Users/mingchuan.yang/Downloads/ASWorker/GradleY/app
应用名称: GradleY App
版本号: 2.0.0
 成功 [执行] 完成::app:printProjectInfo

 Gradle 构建成功

Extension 插件扩展

Extension 示例

xtension是插件提供的配置入口 ,有点类似于Android插件的android { }闭包。

kotlin 复制代码
android {
    compileSdk = 34
    
    defaultConfig {
        minSdk = 24
        targetSdk = 34
    }
}

// 自定义插件的Extension
myPluginConfig {
    enable = true
    appName = "MyApp"
}

我们上边之所以 可以使用 myPluginConfig {} 来设置部分你参数,就是实现了 一个 MyExtension.kt

kotlin 复制代码
abstract class MyExtension {

    //是否启用插件
    abstract val enable: Property

    //应用名称
    abstract val appName: Property

    //版本号
    abstract val versionName: Property

    init {
        enable.convention(true)
        appName.convention("MyApp")
        versionName.convention("1.0.0")
    }
}

Extension 嵌套扩展

我们先创建一个 嵌套的扩展类

kotlin 复制代码
abstract class OuterExtension(project: Project) {

    abstract val name: Property

    // 嵌套的Extension
    val inner: InnerExtension = project.objects.newInstance(InnerExtension::class.java)

    // DSL方法,支持闭包配置
    fun inner(action: Action) {
        action.execute(inner)
    }
}

// 内层Extension
abstract class InnerExtension {
    abstract val enabled: Property
    abstract val debug: Property

    init {
        enabled.convention(true)
        debug.convention(false)
    }
}

定义扩展测试用的 Plugin

kotlin 复制代码
class NestedPlugin : Plugin {
    override fun apply(project: Project) {
        val extension = project.extensions.create(
            "nested",
            OuterExtension::class.java,
            project  // 传递project参数
        )

        project.afterEvaluate {
            println("Name: ${extension.name.get()}")
            println("Enabled: ${extension.inner.enabled.get()}")
            println("Debug: ${extension.inner.debug.get()}")
        }
    }
}

在 gradle-plugin library下的 build.gradle.kts 对新的插件进行注册

kotlin 复制代码
gradlePlugin {
    plugins {
        // 普通插件示范注册省略了这里
        ...
        // 嵌套扩展示例插件
        create("nestedPlugin") {
            id = "com.xmly.nested"
            implementationClass = "com.xmly.plugin.NestedPlugin"
            displayName = "Nested Extension Plugin"
            description = "嵌套扩展插件"
        }
    }
}

在app build.gradle 中进行注册和plugin设置

kotlin 复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    id 'com.xmly.myplugin' version '1.0.0'  // 使用我们发布的插件
    id 'com.xmly.nested' version '1.0.0'
}

...

// 嵌套扩展入口
nested {
    name.set("我的配置")

    inner {
        enabled.set(true)
        debug.set(true)
    }
}

以上示例中的发布都是发布到本地,实际工作中都是 maven地址,大概需要添加的配置包括,maven远端地址 ,签名和 pom等配置,由于公司里的数据,这里我暂时给一个示例

gradle-plugin 文件夹下的 build.gradle.kts

kotlin 复制代码
plugins {
    ...
    signing  // 发布用的
}

group = "com.xmly"
version = "1.0.0"

java {
    withJavadocJar()
    withSourcesJar()
}

dependencies {
    implementation(gradleApi())
}

publishing {
    publications {
        create("maven") {
            from(components["java"])

            pom {
                name.set("xxxx")
                //换成你自己的 maven地址
                url.set("https://xxx")

                licenses {
                    license {
                        name.set("Apache License 2.0")
                        url.set("https://www.apache.org/licenses/LICENSE-2.0")
                    }
                }

                developers {
                    developer {
                        // 这里写自己的开发信息就好
                        id.set("example")
                        name.set("Example Developer")
                        email.set("dev@example.com")
                    }
                }

                scm {
                    // 开源仓库信息
                    connection.set("scm:git:git://xxxx.git")
                    developerConnection.set("scm:git:ssh://xxxx.git")
                    url.set("https://xxxxx/my-plugin")
                }
            }
        }
    }

    repositories {
        maven {
            name = "OSSRH"
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                // 默认都放在 gradle.properties 文件中,不要硬编码 个人使用自己的信息
                username = project.findProperty("ossrhUsername") as String?
                password = project.findProperty("ossrhPassword") as String?
            }
        }
    }
}

// 配置签名 
signing {
    sign(publishing.publications["maven"])
}

一般问 ld 要maven地址即可 ,自己替换申请到的 用户名和密码即可

最后

   这里主要讲本地的 plugin 编写和发布逻辑 总结了一下,远端这里每个公司 应该都有自己的一套 publishMaven 逻辑,所以 不过多编写了, 后边还有 插桩和字节码操作等笔记,不过比较乱,可能输出时间到不固定

参考文档

本文借鉴了很多大佬优秀的文章,感谢下面的文章作者

相关推荐
今日无bug10 小时前
大模型的回答是怎么一个字一个字蹦出来的?前端流式输出解析
前端·llm·agent
计算机魔术师10 小时前
Anthropic 复盘 Claude 模型越权访问真实系统事件并改进对齐与安全措施
前端
掘金者阿豪10 小时前
Seedance 2.0/2.5 虚拟素材能跨 Key 共用吗?一次讲清 Asset ID、账号隔离与 SaaS 素材架构
前端·后端
用户500937683903910 小时前
热敏小票 Web 打印:我在 58/80mm 上折腾的两周
前端·vue.js
pengyu10 小时前
【Kotlin 协程修仙录 · 大乘境 · 初阶】 | 跳出三界:协程在 KMP 与后端开发中的跨平台之道
android·kotlin
XGM10 小时前
一个前端新手的"顿悟"时刻:DOM树、渲染原理和JS动态渲染
前端
XiaoLeisj10 小时前
Android Memory Profiler:堆内存指标、内存抖动与泄漏定位
android·性能优化·内存抖动·内存泄露·memory profiler
未来猫咪花10 小时前
Everything is ViewModel:让状态管理回到对象世界
android·flutter·ios
古法安卓10 小时前
Android-重启流程源码解析
android·java·android studio
东方小月10 小时前
一篇文章带你深入拆解Skill的本质与工程实现,让你不再滥用Skill
前端·人工智能·后端