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

参考文档

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

相关推荐
路修远i2 小时前
前端单元测试
前端·单元测试
不一样的少年_2 小时前
【用户行为监控】别只做工具人了!手把手带你写一个前端埋点统计 SDK
前端·javascript·监控
Cache技术分享2 小时前
270. Java Stream API - 从“怎么做”转向“要什么结果”:声明式编程的优势
前端·后端
king王一帅2 小时前
AI 时代真正流式解析+渲染双重优化的 Incremark
前端·ai编程·markdown
aesthetician3 小时前
用铜钟听歌,发 SCI !
前端·人工智能·音频
二流小码农3 小时前
鸿蒙开发:上架困难?谈谈我的上架之路
android·ios·harmonyos
Mike_jia3 小时前
LogWhisperer 全解析:打造你的Linux服务器AI日志分析中枢
前端
网安Ruler3 小时前
崭新出厂,自研CipherForge小工具,攻破 D-Link M30 固件加密
前端·网络·python
daxiang120922053 小时前
记一次前端请求报错:Content-Length can‘t be present with Transfer-Encoding,+Cursor使用教训
前端·cursor