Gradle 学习之插件

2,781 阅读10分钟

本文内容大部分来自 Gradle 官方文档,英文 OK 的同学可以直接看官方文档。 本文示例代码已放在 zjxstar 的 GitHub

前言

说起 Gradle 插件,不得不感叹 Gradle 的设计非常棒。 Gradle 本身提供基本的概念以及核心框架,而其他的场景逻辑都可以通过插件扩展的方式来实现。对于 Android 开发来说,常用的插件就是 Android Gradle 插件和 Java 插件了,我们会在下一篇文章中详细介绍 Android 插件。

本文主要学习 Gradle 插件的基础知识以及如何编写自定义插件。

插件的作用

Gradle 插件通常用来封装一些可重用的编译逻辑,可以用来扩展项目的功能,帮助项目在构建过程中处理一些特殊逻辑。

  1. 可以给项目中添加自定义任务,比如测试、编译、打包等。
  2. 可以给项目添加依赖配置,比如第三方库等。
  3. 可以向项目中的对象类型添加新的扩展属性、方法等,从而帮助我们配置、优化构建,比如 Android 插件的 android{ } 配置块。
  4. 可以对项目进行一些约定,比如约定 src/main/java 目录下存放源代码。

总之,我们只要根据插件约定的接口,使用它提供的任务、方法或者扩展,就可以帮助我们进行项目构建。

如何应用一个插件

Gradle 中的插件种类分为两种,一种是脚本插件,另一种是二进制插件。而应用插件的方式也有多种,下面我们详细介绍。

脚本插件

所谓的脚本插件就是我们自定义的以 .gradle 为后缀的脚本文件,严格意义上来说,它只是一个可执行脚本。应用它就是把整个脚本加载到主 build.gradle 脚本中,从而辅助构建。应用方式如下:

apply from: '脚本文件名' // 比如:apply from: 'project_task_examples.gradle'

它不仅可以应用本地脚本,也可以应用网络上的脚本,这样的话,就必须使用 HTTP URL 了。

举例说明:

在前两章的 Project 和 Task 的学习中,我们在 app 模块的 build.gradle 文件中写了很多示例,我们把这些示例都提到一个单独的 gradle 脚本中,然后在 build.gradle 中应用它。新的脚本文件命名: project_task_examples.gradle ,只需要使用 apply from: 'project_task_examples.gradle' 应用即可。

project_task_examples.gradle

虽然它不算真正的插件,但也不能忽视它的作用,它是脚本文件模块化的基础。我们可以把庞大的脚本文件进行分块、分段整理,拆分成一个个共用的、职责分明的文件,然后使用 apply from 应用它们。这样可以让我们的脚本更加清晰、简单、方便和快捷。

二进制插件

二进制插件是实现了 org.gradle.api.Plugin 接口的插件(一般都是打包在 jar 里独立发布),它们拥有 plugin id ,这个 id 是插件全局唯一的标识或名称。我们需要通过

apply plugin: 'plugin id'

的方式应用二进制插件。比如 Android 的 Application 插件,我们通过 Android Studio 创建 Android 工程时,在 app 模块中会自动通过 apply plugin: 'com.android.application' 来引入 Application 插件。

对于 Gradle 自带的核心插件都有一个容易记的短名,比如 Java 插件,通过 apply plugin:'java' 应用。其实它对应的类型是 org.gradle.api.plugins.JavaPlugin ,所以还可以通过该类型进行应用:apply plugin:org.gradle.api.plugins.JavaPlugin 。由于包 org.gradle.api.plugins 是默认导入的,所以我们可以去掉包名直接写为: apply plugin:JavaPlugin 。

其实 apply 方法有重载方法:

void apply(Closure var1);

void apply(Action<? super ObjectConfigurationAction> var1);

void apply(Map<String, ?> var1);

之前使用的是传入 Map 参数的方式,我们也可以换成闭包的方式。

apply {
    plugin 'java'
}

使用 plugins DSL 应用插件

plugins DSL 是一种新的插件应用方式,Gradle 2.1 以上版本才可以使用。它的语法如下:

plugins {
    id «plugin id»                                            // (1)
    id «plugin id» version «plugin version» [apply «false»]   // (2)
}

方式(1)是提供给 Gradle 核心插件或者构建脚本中已有的插件的。方式(2)提供给需要解析的二进制插件,这些插件要托管在 plugins.gradle.org/ 网站上。

示例:

plugins {
    id 'java'
}

plugins {
    id 'com.jfrog.bintray' version '0.4.1'
}

当然,这种使用方式有一定的限制:

  1. 《plugin id》和《plugin version》必须是常量字符串。
  2. plugins { } 块必须是 build 脚本中的顶级语句。它不能嵌套在另一个构造中。
  3. plugins { } 块目前只能在项目的构建脚本中使用,不能用于脚本插件、settings.gradle 文件或者 init 脚本。

插件的类型和应用方式就介绍到这,下面我们要学习如何自定义插件,来满足自己的业务逻辑。

自定义插件

自定义插件的关键点就是要实现 org.gradle.api.Plugin 接口,重写 apply 方法来实现自己的业务逻辑。本讲中使用的示例都是构建 Task 的简单示例,感兴趣的同学的可以自行深入。

这里有三种方式来承载自定义插件的代码:构建脚本、buildSrc 项目、独立项目。

构建脚本

我们可以直接在构建脚本中编写自定义插件的代码。 这样做的好处是插件可以自动编译并包含在构建脚本的类路径中,而无需执行任何操作。 但是,这样定义的插件在构建脚本之外是不可见的,即无法在其定义的构建脚本之外重用该插件。

示例:

/* 自定义插件:直接在脚本文件中定义,其局限性较大 */
// app的build.gradle文件中
// 定义一个Extension类用来接收脚本传的参数
class CustomExtensionA {
    String message = 'Hello Custom PluginA'

    // 还可以定义其他配置参数
    String greeter = 'Welcome Gradle Plugin'
}

// 定义一个自定义插件类,实现Plugin接口
class CustomPluginA implements Plugin<Project> {

    @Override
    void apply(Project target) {

        // 将Extension注册给Plugin
        def extension = target.extensions.create('customA', CustomExtensionA)

        // 定义一个任务
        target.task('CustomPluginTaskA') {
            doFirst {
                println 'this is custom plugin A task A'
            }

            doLast {
                // 使用Extension传入的参数
                println extension.message
                println extension.greeter
            }
        }
    }
}

例子中,我们创建一个 CustomPluginA 类,实现 Plugin 接口,重写 apply 方法。在 apply 方法中使用 project 创建一个名为 CustomPluginTaskA 的任务。该插件还可以接受扩展,即定义了一个 CustomExtensionA 类,里面有 message 和 greeter 两个属性。在 apply 方法中,通过 project.extensions.create(扩展名, 扩展类) 就可以完成自定义扩展的注册。

应用该插件只需要在 build.gradle 文件中使用 apply plugin 引入即可。

// 使用插件CustomPluginA,这里必须使用插件类名,而不是字符串
// 使用gradlew -q CustomPluginTaskA查看插件是否生效
apply plugin: CustomPluginA

// 配置CustomExtensionA,需要使用注册名,这里是customA
customA.message = 'Hi from PluginA'

引入插件后,可以像运行普通任务一样通过命令运行插件中定义的 Task 。至于扩展的使用,只需使用扩展名声明对应属性即可。

buildSrc 项目

我们可以在工程里新建一个名为 buildSrc 的模块来开发自定义插件,其过程和写 Android Library 类似。buildSrc 模块中的代码会自动被 Gradle 加载,Gradle 将负责编译和测试插件并使其在构建脚本的类路径中可用。 这种方式定义的插件对于构建中的每个构建脚本都是可见的。 但是,它在构建之外是不可见的,因此无法在其定义的构建之外重用该插件。

新建 buildSrc 模块:

buildSrc自定义插件

该模块非常简单,只需要有 src/main/groovy 目录用来存放源代码即可(因为使用的 Groovy 语言编写,所以是 groovy,如果使用 java 或者 kotlin ,换成对应目录即可)。

在 build.gradle 脚本中依赖 gradleApi 和 Groovy 插件。

// 依赖groovy插件
plugins {
    id 'groovy'
}

dependencies {
    implementation gradleApi()
    implementation localGroovy()
}

接着就是写代码了,这个过程和“构建脚本”小节中一样,只是将代码放在了 groovy 文件中而已。

// 在单独的groovy文件中定义插件类
class CustomPluginB implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.task('CustomPluginTaskB') {
            doFirst {
                println 'This is custom plugin TaskB'
            }
        }
    }
}

最后,就是在 app 模块中引入该插件。

/* 自定义插件:引用在buildSrc模块中定义的插件,使用插件类名的全限定名 */
// 使用gradlew -q CustomPluginTaskB查看插件是否生效
apply plugin: com.zjx.happy.plugin.CustomPluginB

由于构建过程中 Gradle 会自动加载 buildSrc 模块中的代码,所以直接使用插件类的全限定名即可。

独立项目

我们可以为插件创建单独的项目。将该项目发布成一个 JAR ,就可以在多个地方使用它。这种方式是比较常用的。

首先,和 buildSrc 项目一样创建一个新模块,模块名没有要求。

独立项目自定义插件

和 buildSrc 不同的是,它多出了一个 resources 资源目录。同样的,这个模块里也要依赖 gradleApi 和 Groovy 插件。

然后,在 groovy 目录下完成代码的编写。这里依然只是写个简单示例。

class CustomPluginC implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.task('CustomPluginTaskC') {
            doFirst {
                println 'This is Custom Plugin TaskC'
            }
        }
    }
}

至此,插件的核心已经准备好了,现在要给这个插件设置一个 plugin id 。需要在 resources 目录下,创建 META-INF 目录。然后在 META-INF 目录下再创建一个 gradle-plugins 目录。( 注意: 这里创建目录时请逐级创建。不要通过一次性输入 META-INF.gradle-plugins 的目录名方式创建,因为 resources 目录无法像代码目录那样自动识别包名并分级,它只会创建一个目录,这样是不正确的。)

目录创建好后,我们需要在该目录下,新建一个 properties 文件,而文件名就是插件的 plugin id 。该 properties 文件中需要声明插件的实现类,语法如下:

# 创建该目录时一定要注意,要一级一级的创建,先创建META-INF,再创建gradle-plugins,而不是直接META-INF.gradle-plugins
# 因为不是src的目录,所以无法自动识别 . 号
# implementation-class是固定的,等号右边是插件实现类的全限定类名
implementation-class=com.zjx.happy.plugin.CustomPluginC

详细目录结构如图:

resources目录

本示例中的插件的 plugin id 就是 com.happy.custompluginc 。那么 plugin id 有哪些规范呢?

  • 可以包含任何字母、数字、.-
  • 必须至少包含一个 . 将命名空间和插件名分开
  • 通常使用小写反向域名来约定命名空间
  • 插件名仅使用小写字符
  • 命名空间 org.gradle 和 com.gradleware 不可以使用;
  • 不能以 . 开头和结尾
  • 不能使用连续的 . ,比如: ..

插件准备好后,我们要将其发布出去,一般使用 Maven 。

// build.gradle中
apply plugin: 'maven' // 引入 maven 插件

repositories {
    mavenCentral()
}

// 将该插件上传到本地Maven库
group='com.happy.plugin'
version='1.0.0'

// 通过gradlew :customPlugin:uploadArchives命令上传
uploadArchives {
    repositories {
        mavenDeployer {
            //本地的Maven地址设置为../repos
            repository(url: uri('../repos'))
        }
    }
}

使用 gradlew :customPlugin:uploadArchives 命令上传。示例中会在工程目录下创建 repos 目录,里面存放了 customPlugin 的 jar 包和 pom 信息。

repos目录

如何应用独立模块自定义的插件呢?其方法和 Android 插件一样。

在根 build.gradle 文件中声明插件依赖:

dependencies {
    	// 这是Android插件
        classpath 'com.android.tools.build:gradle:3.3.1'
		// 这是刚自定义的插件
        classpath 'com.happy.plugin:customPlugin:1.0.0'
    }

同步之后,就可以在 app 模块中使用 apply plugin 引入了。

/* 自定义插件:引用已经发布到Maven的插件 */
apply plugin: 'com.happy.custompluginc' // 这个plugin id就是properties文件的文件名

最后,使用命令 gradlew -q CustomPluginTaskC 验证插件是否生效。

总结

Gradle 插件相关的内容就这么多,整体上看还是很简单的。现在社区中的第三方插件很多,我们可以根据业务需要适当引用。当然,我们也完全可以自己实现适合业务的插件。一般 APM 类项目中 中就需要实现自定义插件来完成代码的自动插桩,它需要利用到 Android 提供的 Gradle-api 中的 Transform 。后续的文章中我们会学习 Transform 。

参考资料

  1. Gradle 官方文档-使用插件
  2. Gradle 官方文档-自定义插件
  3. 《Android Gradle权威指南》
  4. 《Gradle for Android 中文版》