[Android] How to customise gradle plugin

Posted by xiuyuantech on 2022-09-25

Gradle 和 Gradle 插件是两个概念,
Gradle 插件的本质就是抽取高度模块化的逻辑,以便更高效地进行复用。
Gradle 只是提供了一个构建流程。
而其他可复用的 Task(例如编译 Java 工程、编译 Android 工程等),
是通过应用 Gradle 插件来获取的。
Gradle: 构建工具,提供核心构建流程
Gradle 插件: 本质上是可复用的 task,依赖于 Gradle 环境

基础

gradle 插件比较主流的开发语言是 groovy,因为 gradle 就是用 groovy 写的。
除了 groovy 还可以用来开发 gradle 插件的语言有:java   kotlin   其它基于 jvm 语言
因为 groovy 是基于 jvm 的,所以同样基于 jvm 的也可以用来写 gradle 插件。
但 java 和 kotlin 是静态语言,意味着需要不停地判断类型和强转,相比之下,groovy 作为动态语言就没有这个问题。
通常,使用 java 或 kotlin(静态类型)实现的插件比使用 groovy 实施的插件性能更好。

Gradle 基础 :Wrapper、Groovy、生命周期、Project、Task、增量
Gradle 插件:Plugin、Extension 扩展、NamedDomainObjectContainer、调试
官方文档: 其他BLOG   Gradle Guides   Developing Custom Gradle Plugins

Gradle插件

Gradle 插件的核心类是 Plugin,一般使用 Project 作为泛型实参。当使用方引入插件后,其实就是调用了 Plugin#apply() 方法,我们可以把 apply() 方法理解为插件的执行入口。例如:

1
2
3
4
5
6
public class MyCustomGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "Hello."
}
}

Gradle 插件分为两大类:脚本插件 & 对象插件。
1、脚本插件
脚本插件和一个普通的 build.gradle 文件没什么区别,虽然简单,但它是模块化的基础。
脚本可以存在本地,也可以存在网络上,只需要提供脚本的相对路径或者 URL,例如:
build.gradle 配置 apply plugin: MyCustomGradlePlugin 或者 apply from: xxx.gradle

2、对象插件
对象插件 / 二进制插件: 在一个单独的插件模块中定义,其他模块通过 Plugin ID 应用插件。
因为这种方式发布和复用更加友好,我们一般接触到的 Gradle 插件都是指二进制插件的形式
编写一个对象插件的最低要求是提供一个 org.gradle.api.Plugin 接口的实现类,
这个接口只要一个Plugin#apply(Project)方法。

应用插件

将插件添加到 classpath:

  • 本地依赖: 指直接依赖本地插件源码,一般在调试插件的阶段是使用本地依赖的方式。
    例如:根项目 build.gradle
1
2
3
4
5
6
7
buildscript {
...
dependencies {
// For Debug
classpath project(":MyCustomGradlePlugin")
}
}
  • 远程依赖: 指依赖已发布到 Maven 仓库的插件,一般我们都是用这种方式依赖官方或第三方实现的 Gradle 插件。
    例如:根项目 build.gradle
1
2
3
4
5
6
7
8
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
// 或者使用另一种等价语法:
classpath group: 'com.android.tools.build ', name: 'gradle ', version: '3.5.3'
}
}

使用 apply 应用插件:
在 主项目 build.gradle 文件中,可以使用 apply 方法来应用插件,有两种语法:

1
2
3
4
5
6
apply plugin: 'com.demo.MyCustomGradlePlugin'
//或者
plugins {
// id «plugin id» [version «plugin version»] [apply «false»]
id 'com.android.MyCustomGradlePlugin'
}

这里的 id 是插件的简短名称,是在插件中resources/META-INF/gradle-plugins/[id].properties文件的文件名决定的。
注意: 在一个 build.gradle 中不要同时使用这两种语法。

自定义插件

自定义插件三种方式:
build script:在build.gradle脚本中直接编写,只能在本文件内使用;
buildSrc项目:新建一个名为buildSrc的Module,只能在本项目中使用;
独立的项目(standalone project):在独立的项目中编写插件,发布到本地或者远程maven仓库供其他项目使用。

  • build script
  1. app module下的build.gradle脚本文件编写插件。
1
2
3
4
5
6
7
8
9
public class MyCustomGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "Hello."
}
}

//应用插件
apply plugin: MyCustomGradlePlugin
  1. 根目录下创建test.gralde文件并添加脚本内容。
1
2
3
4
5
6
7
8
public class MyCustomGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "Hello."
}
}

apply plugin: MyCustomGradlePlugin

app module下的build.gradle中添加apply from: '…/test.gradle’即可应用test.gralde脚本。

1
apply from: '../test.gradle'
  • buildSrc 项目
    在工程中新建buildSrc目录,名称一定不能写错,在buildSrc目录下建立如下目录和文件:
1
2
3
4
src/
main/
groovy/
build.gradle

编辑build.gradle文件,添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apply plugin: 'kotlin'
//或者
apply plugin: 'java'
//或者
apply plugin: 'groovy'

buildscript {
repositories {
jcenter()
google()
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
}
}

allprojects {
repositories {
jcenter()
google()
}
}

dependencies {
implementation gradleApi()
implementation localGroovy()
implementation 'com.android.tools.build:gradle:3.5.0'
}

点击sync或者buid project(这一步必须),会生成build目录,groovy目录也会变成源文件目录,之后就可以创建包和源代码。

踩坑:
buildSrc 模块会被自动识别为参与构建的模块,因此不需要在 settings.gradle 中使用 include 引入,就算引入了也会编译出错.
buildSrc 模块会自动被添加到构建脚本的 classpath 中,不需要手动添加.
buildSrc 模块的 build.gradle 执行时机早于其他 Project.

  • standalone project
    新建一个module(新建一个工程也可以,因为最后生成和使用都是jar包的形式),
    module名称随意,只保留build.gradle文件和src/main目录,其余文件全部删掉。
    修改build.gradle文件内容,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apply plugin: 'maven' //要想发布到Maven,此插件必须使用
apply plugin: 'groovy'

buildscript {
repositories {
jcenter()
google()
mavenCentral() //必须
}

dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
}
}

allprojects {
repositories {
jcenter()
google()
mavenCentral() //必须
}
}

dependencies {
implementation gradleApi()
implementation localGroovy()
implementation 'com.android.tools.build:gradle:3.5.0'
}

def group='com.demo.plugin' //组
def artifactId='MyCustomGradlePlugin' //唯一标示
def version='1.0.0' //版本

//本地地址:/Users/username(你电脑的名称)/.hhrepo
def uploadRepo = new File(System.getProperty("user.home") + "/.hhrepo")
//将插件打包上传到本地maven仓库
uploadArchives {
repositories {
mavenDeployer {
pom.groupId = group
pom.artifactId = artifactId
pom.version = version
//指定本地maven的路径,在项目根目录下
repository(url: uri(uploadRepo))
}
}
}

编写插件:

1
2
3
4
5
6
public class MyCustomGradlePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "Hello."
}
}

配置别名步骤:
main目录下新建resources/META-INF/gradle-plugins目录,名称不能随意填写。
gradle-plugins下新建name(插件名).properties文件,这个name就是后面要使用的插件名。
例如:

1
2
MyCustomGradlePlugin.properties //别人引用即可直接写 apply plugin: MyCustomGradlePlugin
implementation-class=com.demo.plugin.MyCustomGradlePlugin

提示: 你可以通过 sourceSets {} 闭包方法来设置 resources 文件夹的位置。

发布:
在终端中执行./gradlew uploadArchives指令,或者展开Android Studio右侧的Gradle,
找到对应module->uploadArchivesTask,就可以将插件部署到相应的目录下。

插件调试

注意其中插图是引入他人的,不必较真,只是为了大家在UI上直观找到对应到自己项目中配置即可

  • 方法 1(简单)
    直接提供 Android Studio 中 Gradle 面板的调试功能,即可调试插件。如下图,我们选择与插件功能相关的 Task,并右键选择 Debug 执行。
    Debug

  • 方法 2(通过配置 IDE Configuration 以支持调试命令行任务)
    1、创建 Remote 类型 Configuration:
    Configuration
    2、执行命令: ./gradlew Task -Dorg.gradle.debug=true --no-daemon (开启 Debug & 不使用守护进程),执行后命令行会进入等待状态:
    Order
    3、Attach Debug: 点击调试按钮,即可开始断点调试。
    Attach

插件开发技巧

判断是否当前是 App 模块还是 Library 模块: 当我们开发 Android 项目相关插件时,经常需要根据插件的使用环境区分不同逻辑。
例如插件应用在 App 模块和 Library 模块会采用不同逻辑。此时,我们可以用在 Plugin#apply() 中采用以下判断:

1
2
3
4
5
6
project.afterEvaluate {
// 1. Check if apply the ‘com.android.application’ plugin
if (!project.getPluginManager().hasPlugin("com.android.application")) {
return
}
}

总结

开发gradle插件掌握方式三standalone project就行了,关键是项目里面有相关的内容能看懂就行了。
其他比如字节码插桩、编译优化等高级开发都会涉及AGP的内容,参考链接: Gradle插件系列   调试Gradle插件   自定义Gradle插件