Gradle 自定义插件
自定义 Gradle 插件的本质就是把逻辑独立的代码进行抽取和封装,以便于我们更高效地通过插件依赖这一方式进行功能复用。
要创建 Gradle 插件,您需要编写一个实现 Plugin 接口。将插件应用于项目时,Gradle 将创建插件类的实例,并调用该实例的 Plugin.apply()
方法。项目对象作为参数传递,插件可以使用它来配置项目。
在 Android 下的 Gradle 插件共分为 两大类,如下所示:
- 脚本插件:同普通的 Gradle 脚本编写形式一样,通过
apply from: 'config.gradle'
引用。 - 对象插件:通过插件全路径类名或 id 引用,它主要有 三种编写形式:
- 在当前构建脚本下直接编写
- 在 buildSrc 目录下编写
- 在完全独立的项目中编写
方式一:build Script
直接在 build.gradle
文件中编写插件,将实现 Plugin 的代码从单独的目录或 buildSrc 直接移动到·bubild.gradle·中去
- 不需要配置
META-INF
,直接 apply 就行。
打开 app 模块下的 build.gradle 文件,在其中编写一个类实现 Plugin 接口:
1
2
3
4
5
6
7
8
9
10
11
| // app模块下的build.gradle文件中定义对象插件
class CustomGradlePlugin implements Plugin<Project>{
@Override
void apply(Project target) {
target.task("showCustomPlugin"){
doLast {
println("this is CustomGradlePlugin")
}
}
}
}
|
然后通过插件类名引用它:
1
2
| // app模块下的build.gradle文件中,使用对象插件
apply plugin: CustomGradlePlugin
|
执行插件中定义的 task:
1
2
3
4
| ./gradlew showCustomPlugin
# 或者加一个 -q 参数,省略多与信息
gradlew -q showCustomPlugin
|
方式二:buildSrc (多 module 复用)
buildSrc 目录是 gradle 默认的构建目录之一,该目录下的代码会在构建时自动地进行编译打包,然后它会被添加到 buildScript 中的 classpath 下,所以不需要任何额外的配置,就可以直接被其他模块中的 gradle 脚本引用。
- buildSrc 的执行时机不仅早于任何⼀个 project(build.gradle),而且也早于 settings.gradle
- settings.gradle 中如果配置了 ‘:buildSrc’ ,buildSrc ⽬录就会被当做是子 Project , 因会它会被执行两遍。所以在 settings.gradle 里面应该删掉 ‘:buildSrc’ 的配置
创建插件步骤
- 新建一个 module,并将其命名为 buildSrc。这样,Gradle 默认会将其识别会工程的插件目录,将其从 settings.gradle 中删除。
- src 目录下删除仅保留一个空的 main 目录,并在 main 目录下新建 1 个 groovy 目录与 1 个 resources 目录
- 将 buildSrc 中的 build.gradle 中的所有配置删去,并配置 groovy、resources 为源码目录与相关依赖即可
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
| apply plugin: 'groovy'
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
// Groovy DSL
implementation localGroovy()
// Gradle DSL
implementation gradleApi()
// Android DSL
implementation 'com.android.tools.build:gradle:3.6.2'
// ASM V7.1
implementation group: 'org.ow2.asm', name: 'asm', version: '7.1'
implementation group: 'org.ow2.asm', name: 'asm-commons', version: '7.1'
}
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
resources {
srcDir 'src/main/resources'
}
}
}
|
- 在我的 main 目录下创建一个递归文件夹 “me.hacket.study”,里面直接新建一个名为 CustomGradlePlugin 的普通文件。然后,在文件中写入 ‘class CustomGradlePlugin’ ,这时 CustomGradlePlugin 会被自动识别为类,接着将其实现 Plugin 接口,其中的 apply 方法就是插件被引入时要执行的方法,这样,自定义插件类就基本完成了,CustomGradlePlugin 类的代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
| /**
* 自定义插件
*/
class CustomGradlePlugin implements Plugin<Project> {
/**
* 插件被引入时要执行的方法
* @param project 引入当前插件的 project
*/
@Override
void apply(Project project) {
println "Hello plugin..." + project.name
}
}
|
- 接着,在 resources 目录下创建一个 META-INF.gradle-plugins 的递归目录,里面新建一个 “me.hacket.study.properties” 文件,其中 ‘.properties’ 前面的名字即为 自定义插件的名字,在该文件中,我们需要标识该插件对应的插件实现类。一个最简单的自定义插件就完成了。
1
| implementation-class=me.hacket.study.CustomGradlePlugin
|
- 直接在 app module 下的 build.gradle 文件中使用 apply 引入插件
1
| apply plugin: 'me.hacket.study'
|
自定义 Extension 与 Task
自定义 Extension
1
2
3
4
5
6
7
8
| class ReleaseInfoExtension {
String versionName;
String versionCode;
String versionInfo;
String fileName;
}
// 创建用于设置版本信息的扩展属性
project.extensions.create("releaseInfo", ReleaseInfoExtension.class)
|
在 project.extensions.create 方法的内部其实质是 通过 project.extensions.create() 方法来获取在 releaseInfo 闭包中定义的内容并通过反射将闭包的内容转换成一个 ReleaseInfoExtension 对象
最后,我们就可以在 app module 的 build.gradle 脚本中使用 releaseInfo 去配置扩展属性:
1
2
3
4
5
6
| releaseInfo {
versionCode = "1"
versionName = "1.0.0"
versionInfo = "第一个版本~"
fileName = "releases.xml"
}
|
自定义 Task
使用自定义扩展属性 Extension 仅仅是为了让使用插件者有配置插件的能力。而插件还得借助自定义 Task 来实现相应的功能,这里我们需要创建一个更新版本信息的 Task。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class ReleaseInfoTask extends DefaultTask {
ReleaseInfoTask() {
// 1、在构造器中配置了该 Task 对应的 Task group,即 Task 组,并为其添加上了对应的描述信息。
group = 'version_manager'
description = 'release info update'
}
// 2、在 gradle 执行阶段执行
@TaskAction
void doAction() {
updateVersionInfo();
}
}
// 创建用于更新版本信息的 task
project.tasks.create("releaseInfoTask", ReleaseInfoTask.class)
|
如何引用 buildSrc 中的插件
注意这里引用的方式可以是通过类名引用,也可以通过给插件映射一个 id,然后通过 id 引用。
- 全路径类名引用
通过类名引用插件的需要使用全限定名,也就是需要带上包名,或者可以先导入这个插件类,如下:
1
2
3
4
5
| // 在app模块下的build.gradle文件中引用
apply plugin:com.wings.gradle.CustomBuildSrcPlugin
// 或者:
import com.wings.gradle.CustomBuildSrcPlugin
apply plugin: CustomBuildSrcPlugin
|
- id 映射引用
通过简单的 id 的方式,我们可以隐藏类名等细节,使的引用更加容易。映射方式很简单,在 buildSrc 目录下创建 resources/META-INF/gradle-plugins/xxx.properties
,这里的 xxx 也就是所映射的 id,这里我们假设取名 CustomPlugin。具体结构可参考上文 buildSrc 目录结构。
CustomPlugin.properties 文件中配置该 id 所对应的 plugin 实现类:
1
| implementation-class=com.wings.gradle.CustomBuildSrcPlugin
|
此时就可以通过 id 来引用对于的插件了:
1
2
| // 在app模块下的build.gradle文件中引用
apply plugin: 'CustomPlugin'
|
buildSrc 插件案例
- 在
src/main/groovy/me.hacket.plugins/
编写插件代码
1
2
3
4
5
6
7
8
9
10
11
12
| package me.hacket.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
class HelloWorldPlugin implements Plugin<Project> {
@Override
void apply(Project target) {
println("======================== >>>>>>>>>>>>>>>> HelloWorldPlugin $target")
}
}
|
- 配置插件,在
src/main/resources/META-INF/gradle-plugins
,新建 me-hacket-helloworld.properties
,内容为 Plugin 实现的全路径
1
| implementation-class=me.hacket.plugins.HelloWorldPlugin
|
- 应用插件
1
| apply plugin: 'me-hacket-helloworld'
|
Composing builds 和 buildSrc
方式三:独立 module (多工程复用)
在 buildSrc 下创建的 plugin 只能在该工程下的多个模块之间复用代码。如果想要在多个项目之间复用这个插件,我们就需要在一个单独的工程中编写插件,将编译后的 jar 包上传 maven 仓库。
buildSrc 下的代码在构建时会自动编译并被引用;在独立项目中编写的插件如果要能正确的被引用到,需要上传到 maven 仓库中,然后显式地在需要引用的项目中的 buildSrcipt 中添加对该构件的依赖。
- 创建一个 module
- 在
build.gradle
添加插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| apply plugin: 'groovy'
// 发布插件
apply plugin: 'maven-publish'
// 插件发布的配置
publishing {
publications {
Jiagu(MavenPublication) {
from components.java
groupId 'me.hacket'
artifactId 'jiagu360'
version '2.0'
}
}
}
dependencies {
// 依赖的插件:依赖官方agp插件
implementation 'com.android.tools.build:gradle:4.1.3'
// 可跳转到源码
implementation gradleApi()
}
|
- 在
src/main/groovy/me.hacket.jiagu/
包下创建插件代码
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
| // JiaguPlugin.groovy
class JiaguPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
Jiagu jiagu = project.extensions.create("jiagu360", Jiagu)
def userName = jiagu.userName
def password = jiagu.password
def jiaguTools = jiagu.jiaguTools
println("------>>> userName=$userName, password=$password, jiaguTools=$jiaguTools")
// 在gradle配置完成之后回调,在解析完build.gradle之后回调
project.afterEvaluate {
def userName1 = jiagu.userName
def password1 = jiagu.password
def jiaguTools1 = jiagu.jiaguTools
println("------>>> afterEvaluate userName1=$userName1, password1=$password1, jiaguTools1=$jiaguTools1")
AppExtension android = project.extensions.android
android.applicationVariants.all { ApplicationVariant variant ->
variant.outputs.all { BaseVariantOutput output ->
println("------>>> variant=${variant.baseName.capitalize()}, output=${output.baseName}")
// 对应variant的签名配置(debug/release)
SigningConfig signingConfig = variant.signingConfig
// 输出的apk文件
File apkOutputFile = output.outputFile
// 创建加固task
JiaguTask task = project.tasks.create("jiagu${variant.baseName.capitalize()}", JiaguTask)
task.jiagu = jiagu
task.signingConfig = signingConfig
task.apk = apkOutputFile
}
}
}
}
}
// Jiagu.groovy
class Jiagu {
String userName;
String password
String jiaguTools
String getUserName() {
return userName
}
void setUserName(String userName) {
this.userName = userName
}
String getPassword() {
return password
}
void setPassword(String password) {
this.password = password
}
String getJiaguTools() {
return jiaguTools
}
void setJiaguTools(String jiaguTools) {
this.jiaguTools = jiaguTools
}
}
// JiaguTask.groovy
class JiaguTask extends DefaultTask {
Jiagu jiagu
SigningConfig signingConfig
File apk
JiaguTask() {
group = "jiagu"
}
@TaskAction
def run() {
// 调用命令行工具
project.exec { it ->
// java -jar jiagu.jar -login userName password
it.commandLine("java", "-jar", jiagu.jiaguTools, "-login", jiagu.userName, jiagu.password)
}
if (signingConfig) {
project.exec {
// java -jar jiagu.jar -importsign xxx
it.commandLine("java", "-jar", jiagu.jiaguTools, "-importsign", signingConfig.storeFile.absolutePath,
signingConfig.storePassword, signingConfig.keyAlias, signingConfig.keyPassword)
}
}
project.exec {
// java -jar jiagu.jar -jiagu xxx
it.commandLine("java", "-jar", jiagu.jiaguTools, "-jiagu", apk.absolutePath, apk.parent, "-autosign")
}
}
}
|
- 配置插件,在
src/main/resources/META-INF/gradle-plugins
,新建 me.hacket.jiagu.properties
,内容为 Plugin 实现的全路径
1
| implementation-class=me.hacket.jiagu.JiaguPlugin
|
- 使用
maven-publish
发布到 mavenLocal - 引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 项目根目录build.gradle
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'me.hacket:jiagu360:2.0'
}
}
// app.gradle中使用插件
apply plugin: 'me.hacket.jiagu360'
jiagu360 {
userName '592645357@qq.com'
password '*#zfs1314520'
jiaguTools '/Users/zengfansheng/Hacket/Workspace/king-assist/config/tools/jiagu/jiagu.jar'
}
|