文章

02.Gradle 自定义插件 legacy

02.Gradle 自定义插件 legacy

Gradle 自定义插件

自定义 Gradle 插件的本质就是把逻辑独立的代码进行抽取和封装,以便于我们更高效地通过插件依赖这一方式进行功能复用。

要创建 Gradle 插件,您需要编写一个实现 Plugin 接口。将插件应用于项目时,Gradle 将创建插件类的实例,并调用该实例的 Plugin.apply() 方法。项目对象作为参数传递,插件可以使用它来配置项目。

在 Android 下的 Gradle 插件共分为 两大类,如下所示:

  1. 脚本插件:同普通的 Gradle 脚本编写形式一样,通过 apply from: 'config.gradle' 引用。
  2. 对象插件:通过插件全路径类名或 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 脚本引用。

  1. buildSrc 的执行时机不仅早于任何⼀个 project(build.gradle),而且也早于 settings.gradle
  2. settings.gradle 中如果配置了 ‘:buildSrc’ ,buildSrc ⽬录就会被当做是子 Project , 因会它会被执行两遍。所以在 settings.gradle 里面应该删掉 ‘:buildSrc’ 的配置

创建插件步骤

  1. 新建一个 module,并将其命名为 buildSrc。这样,Gradle 默认会将其识别会工程的插件目录,将其从 settings.gradle 中删除。
  2. src 目录下删除仅保留一个空的 main 目录,并在 main 目录下新建 1 个 groovy 目录与 1 个 resources 目录
  3. 将 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'
        }
    }
}
  1. 在我的 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
    }
}
  1. 接着,在 resources 目录下创建一个 META-INF.gradle-plugins 的递归目录,里面新建一个 “me.hacket.study.properties” 文件,其中 ‘.properties’ 前面的名字即为 自定义插件的名字,在该文件中,我们需要标识该插件对应的插件实现类。一个最简单的自定义插件就完成了。
1
implementation-class=me.hacket.study.CustomGradlePlugin
  1. 直接在 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 插件案例

  1. 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")
    }
}
  1. 配置插件,在 src/main/resources/META-INF/gradle-plugins,新建 me-hacket-helloworld.properties,内容为 Plugin 实现的全路径
1
implementation-class=me.hacket.plugins.HelloWorldPlugin
  1. 应用插件
1
apply plugin: 'me-hacket-helloworld'

Composing builds 和 buildSrc

方式三:独立 module (多工程复用)

在 buildSrc 下创建的 plugin 只能在该工程下的多个模块之间复用代码。如果想要在多个项目之间复用这个插件,我们就需要在一个单独的工程中编写插件,将编译后的 jar 包上传 maven 仓库。

buildSrc 下的代码在构建时会自动编译并被引用;在独立项目中编写的插件如果要能正确的被引用到,需要上传到 maven 仓库中,然后显式地在需要引用的项目中的 buildSrcipt 中添加对该构件的依赖。

  1. 创建一个 module
  2. 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()
}
  1. 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")
        }
    }
}
  1. 配置插件,在 src/main/resources/META-INF/gradle-plugins,新建 me.hacket.jiagu.properties,内容为 Plugin 实现的全路径
1
implementation-class=me.hacket.jiagu.JiaguPlugin
  1. 使用 maven-publish 发布到 mavenLocal
  2. 引用
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'
}
本文由作者按照 CC BY 4.0 进行授权