本文内容大部分来自 Gradle 官方文档,英文 OK 的同学可以直接看官方文档。 本文示例代码已放在 zjxstar 的 GitHub。
前言
说起 Gradle 插件,不得不感叹 Gradle 的设计非常棒。 Gradle 本身提供基本的概念以及核心框架,而其他的场景逻辑都可以通过插件扩展的方式来实现。对于 Android 开发来说,常用的插件就是 Android Gradle 插件和 Java 插件了,我们会在下一篇文章中详细介绍 Android 插件。
本文主要学习 Gradle 插件的基础知识以及如何编写自定义插件。
插件的作用
Gradle 插件通常用来封装一些可重用的编译逻辑,可以用来扩展项目的功能,帮助项目在构建过程中处理一些特殊逻辑。
- 可以给项目中添加自定义任务,比如测试、编译、打包等。
- 可以给项目添加依赖配置,比如第三方库等。
- 可以向项目中的对象类型添加新的扩展属性、方法等,从而帮助我们配置、优化构建,比如 Android 插件的 android{ } 配置块。
- 可以对项目进行一些约定,比如约定 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' 应用即可。
虽然它不算真正的插件,但也不能忽视它的作用,它是脚本文件模块化的基础。我们可以把庞大的脚本文件进行分块、分段整理,拆分成一个个共用的、职责分明的文件,然后使用 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'
}
当然,这种使用方式有一定的限制:
- 《plugin id》和《plugin version》必须是常量字符串。
- plugins { } 块必须是 build 脚本中的顶级语句。它不能嵌套在另一个构造中。
- 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 模块:
该模块非常简单,只需要有 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
详细目录结构如图:
本示例中的插件的 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 信息。
如何应用独立模块自定义的插件呢?其方法和 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 。
参考资料
- Gradle 官方文档-使用插件
- Gradle 官方文档-自定义插件
- 《Android Gradle权威指南》
- 《Gradle for Android 中文版》