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 逻辑,所以 不过多编写了, 后边还有 插桩和字节码操作等笔记,不过比较乱,可能输出时间到不固定

参考文档

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

相关推荐
VT.馒头21 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
有位神秘人1 天前
Android中Notification的使用详解
android·java·javascript
·云扬·1 天前
MySQL Binlog落盘机制深度解析:性能与安全性的平衡艺术
android·mysql·adb
phltxy1 天前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
独自破碎E1 天前
【BISHI9】田忌赛马
android·java·开发语言
Byron07071 天前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js
css趣多多1 天前
地图快速上手
前端
zhengfei6111 天前
面向攻击性安全专业人员的一体化浏览器扩展程序[特殊字符]
前端·chrome·safari
码丁_1171 天前
为什么前端需要做优化?
前端
代码s贝多芬的音符1 天前
android 两个人脸对比 mlkit
android