一、插件作用
Gradle插件可以做什么呢?主要有以下的几点
- 为项目配置依赖。
- 为项目配置约定,比如约定源代码的存放位置。
- 为项目添加任务,完成测试、编译、打包等任务。
- 为项目中的核心对象和其他插件的对象添加拓展类型。
使用Gradle插件主要有以下的好处:
- 重用和减少维护在多个项目类似的逻辑的开销。
- 更高程度的模块化。
- 封装必要的逻辑,并允许构建脚本尽可能是声明性地。
二、插件分类
Gradle 中插件可以分为两类:脚本插件和二进制插件。要想应用插件,主要有两个步骤,其一:解析插件,其二:应用插件。下面就这两种类型的插件来做介绍。更多信息可参见官网
2.1 脚本插件
脚本插件 其实就是以 .gradle 为后缀的配置脚本,这里我们定义一个脚本插件:config.gradle:
ext{ myName = "myworld" verson='1.0' url='www.baidu.com' }
|
这里只是定义了一个简单的脚本,事实上里面可以定义一些的任务去执行特定的操作。现在有了这样的一个脚本插件,该如何引用呢:
apply from: 'config.gradle' task myTask { doLast { println "我的名字为:${myName}, 版本为:${verson}, 地址为:${url}" } }
|
可见对于脚本插件来说,是使用 apply from 的方式来引用,在根目录下执行 ./gradle myTask 即可打印出结果。这里来简单说一下 apply 方法,这个方法是定义在 PluginAware 接口中,原型如下:
public interface PluginAware { PluginContainer getPlugins();
void apply(Closure var1); void apply(Action<? super ObjectConfigurationAction> var1); void apply(Map<String, ?> var1); PluginManager getPluginManager(); }
|
这里 apply 可以接收三种不同的参数,那么对应的就应该有三种方式来引用插件:
apply { from "config.gradle" }
apply(new Action<ObjectConfigurationAction>() { @Override void execute(ObjectConfigurationAction objectConfigurationAction) { objectConfigurationAction.from("config.gradle") } })
apply from: "config.gradle"
|
2.2 对象插件
对象插件(或称二进制插件), 是继承 Plugin 接口的插件,对象插件可以分为内部插件和第三方插件。所谓内部插件是指 Gralde 内置的大量的常用插件,如官网核心插件所示,例如 java 插件和 C 的插件,我们可以直接引用:
apply plugin:'java' apply plugin:'cpp
|
每一个插件在发布的时候都会指定唯一的一个插件ID,上面就是通过插件ID的方式来引用的,其实真正的 Java 插件全称是 JavaPlugin,应用方式如下:
第三方插件通常是以 Jar 包形式存在,要想让构建脚本知道第三方插件的存在,需要使用 buildscrip 来配置 classpath,如 Android Gradle 这个插件:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.2' } } apply plugin: 'com.android.application'
|
在 buildscrip 中来定义插件所在的原始仓库和插件的依赖 ,再通过 apply 方法配置即可
三、自定义插件
脚本插件本质上就是一个 Gradle 构建脚本,这里不在赘述,本小节着重介绍如何实现一个对象插件。从官网Custom Plugins上可知,有如下三个地方可以用来存放插件的源码:
3.1 Build script
这种方式是直接在已有的 gradle 构建脚本中直接添加插件代码(以build.gradle为例):
class MyPlugin implements Plugin<Project>{ @Override void apply(Project project) { println("MyPlugin 执行了")
project.task("MyTask"){ doLast { println("MyTask执行了") } } } }
apply plugin: MyPlugin
|
这个就是直接在build.gradle 中实现了一个插件,在插件中定义了一个MyTask任务,但是这个插件只能在本文件中使用,我们直接执行Mytask任务 ./gradlew MyTask ,得到如下结果:
> Configure project :app MyPlugin 执行了
> Task :app:MyTask MyTask执行了
|
这种方式并不常用,虽然这种形式的插件会自动编译并包含在构建脚本的类路径中,但这种方式局限性太大,只能本 Project 使用,其他的 Project 不能使用
3.2 buildSrc project
buildSrc 是默认的插件目录,Gradle 会自动编译和测试其中的代码并将其添加到构建脚本的 classpath 中,因此不需要单独 included build 。相较于脚本插件,此种类型更容易维护、重构和测试。
一个包含 buildSrc 的工程,其项目结构如下。buildSrc下 的代码使用跟应用代码类似的包。如果有额外的配置需要,buildSrc 目录可以放一个可选的构建脚本(比如,使用插件或声明依赖):
. ├── build.gradle ├── buildSrc │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── zsk │ └── plugin │ └── MyPlugin.java ├── otherModule ├── gradlew ├── gradlew.bat └── settings.gradle
|
这里直接在根目录下创建 buildSrc 目录,如果采用 Java 开发插件,则前面三级目录为 src/main/java ,如果是 Groovy 则目录为 src/main/groovy ,kotlin 同理;剩下的则为插件包名可以自行定义。开发插件重要的有两点,这里我们以 Java 为示例:
1. buildSrc/build.gradle :
在 buildSrc 目录下创建 build.gradle,内容如下:
plugins { id 'java-gradle-plugin' // ① }
gradlePlugin { plugins { greeting { // ② id = 'com.zsk.plugin' // ③ implementationClass = 'com.zsk.plugin.MyPlugin' // ④ } } }
|
- ① : 用于在 Gradle 构建环境中支持 Java 插件开发的插件
- ② : 给插件定义名称
- ③ : 插件唯一标识符,用于插件引用,通常采用 groupId.artifactId 格式
- ④ : 实现插件逻辑的Java类的全限定名(包括包名和类名),Gradle会使用这个类来实例化插件,并调用其方法来执行插件的功能
2. 插件实现类
package com.zsk.plugin; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Action; public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) { System.out.println("这是我的第一个插件!"); project.getTasks().create("MyTask", task -> { task.doLast(task1 -> System.out.println("插件任务 MyTask 执行!")); }); } }
|
定义 MyPlugin 类实现 Plugin 接口,并在插件中定义了一个 MyTask 的任务,在根目录下执行 ./gradlew MyTask 命令:
> Configure project : 这是我的第一个插件!
> Task :MyTask 插件任务 MyTask 执行!
|
上面是将 MyTask 直接定义在插件类中,我们可以在 buildSrc 目录中单独定义任务类 MyTask2,然后在 Plugin 类中直接引用:
package com.zsk.plugin;
import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction;
public class MyTask2 extends DefaultTask { @TaskAction void taskAction() { System.out.println("插件任务 MyTask2 执行!"); } }
|
在 MyPlugin 直接引用:
public class MyPlugin implements Plugin<Project> { @Override public void apply(Project project) {
project.getTasks().create("MyTask2", MyTask2.class); } }
|
3.3 Standalone project
buildSrc 的形式限定了只能在本工程中使用,而其他的项目工程不能使用,因此我们可以将插件移动至独立项目,以便发布并与他人共享。这项目只是一个 Java 项目,生成包含插件类的 Jar,打包和发布可以利用 Gradle 中内置的插件: java-gradle-plugin 。这里我们还是脱离其他平台,而只使用 Gradle 本身的命令来实现。其实其他平台例如 Android Studio 在创建发布插件的流程和发布一个普通的 Jar 包过程完全一致。
- 新建目录 standalonePlugin,并准备 gradle 的执行环境
- 根目录下执行如下指令,其中选项可根据实际自行配置
./gradlew init --type java-gradle-plugin
|
这样一个独立的插件已经创建完成!目录结构如下:
├── gradle ├── gradlew ├── gradlew.bat ├── plugin └── settings.gradle
|
其中已经自动生成了一个插件类以及一个默认的任务:
package cn.zsk.plugin;
import org.gradle.api.Project; import org.gradle.api.Plugin;
public class MyPlugin implements Plugin<Project> { public void apply(Project project) { project.getTasks().register("greeting", task -> { task.doLast(s -> System.out.println("Hello from plugin 'cn.zsk.plugin.greeting'")); }); } }
|
现在已经有了这样的一个插件工程,接下来要做的就是将这个工程上传至 Maven 以便分享给其他人使用,现在来修改 plugin/build.gradle :
plugins { id 'java-gradle-plugin' id 'maven-publish' }
......
gradlePlugin { plugins { greeting { id = 'cn.zsk.plugin' implementationClass = 'cn.zsk.plugin.MyPlugin' } } }
publishing { publications { maven(MavenPublication) { groupId = 'cn.zsk.plugin' artifactId = 'MyPlugin' version = '1.1.0'
from components.java } } repositories { maven { url = uri('http://xxx.xxx.xxx-xxx.com/xxx/maven-releases/') credentials { username = "xxx" password = "xxx" } allowInsecureProtocol true } } } ......
|
在根目录下执行如下指令:
./gradlew publishMavenPublicationToMavenRepository
|
具体为什么是这个指令,目前猜测是和 publishing 模块中的 MavenPublication 属性有关系,若是直接使用 ./gradlew publish 会有额外的信息提交到 Maven 仓库上。留到后续在补充。
- 如何使用
上面我们已经将插件上传至 Maven 仓库中了,接下来我们演示如何使用这个插件:
1、在 setting.gradle 中配置仓库地址:
pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() maven { url 'http://xxx.xxx.xxxx.com/repository/maven-public/' allowInsecureProtocol = true } } }
|
2、在根目录下的 build.gradle 中生命插件的类路径:
buildscript { dependencies { classpath 'cn.zsk.plugin:MyPlugin:1.1.0' } }
|
3、在 app/build.gradle 中应用插件:
plugins { ... id 'cn.zsk.plugin' }
|
4、验证插件:
//根目录下执行 ./gradlew greeting 可得:
> Task :app:greeting Hello from plugin 'cn.zsk.plugin.greeting'
|
3.4 插件扩展
假如我们希望在插件运行的时候响应外界的输入,那就使得插件具有更高的灵活性。这就需要用到 Extension 属性来实现。这里还是在 3.3 小节中创建的插件工程上来添加扩展。
首先建立一个实体类,用于接收参数:
package cn.zsk.plugin;
public class MyExtension { String pluginName; int version; }
|
在插件类中添加扩展
package cn.zsk.plugin;
import org.gradle.api.Project; import org.gradle.api.Plugin;
public class MyPlugin implements Plugin<Project> { public void apply(Project project) {
project.extensions.add('myExtension',MyExtension);
project.getTasks().register("showExtension", task -> { task.doLast(s -> { MyExtension extension = (MyExtension) project.getExtensions().getByName("myExtension"); System.out.println("Extension : pluginName = "+extension.pluginName +" version = "+extension.version); }); }); } }
|
修改一下版本重新上传 Maven 仓库,之后就可以在 module 中使用了:
myExtension { pluginName = "zskPlugin" version = 3 }
|
注意这里是采用 = 好来为扩展插件来赋值,这和常见的 Android 中的 android{…} 块不太一样,是省略 “=” 号,如下所示:
android { namespace 'com.example.jnidemo' compileSdk 34
defaultConfig { applicationId "com.example.jnidemo" minSdk 24 targetSdk 34 versionCode 1 versionName "1.0" }
... }
|
Android Gradle Plugin(AGP)是 Gradle 的一个特殊插件,它专门为Android项目的构建和管理而设计。AGP 引入了许多自定义的任务、类型和扩展,这些都是为了简化 Android 应用的构建过程。在 AGP中,一些配置属性(如 compileSdkVersion、minSdkVersio n等)允许省略等号(=)进行配置,这是因为在 AG P的 DSL 设计中,这些属性被特别处理以支持更简洁的语法。
这种语法糖(syntactic sugar)是 AGP 团队为了提升Android开发者体验而引入的,并不是 Gradle 本身的标准特性。它利用了 Groovy 的灵活性和Gradle DSL 的扩展性,但在其他非 Android 的 Gradle 插件中并不常见,对于一般的 Gradle 插件,Gradle 没有提供类似的语法糖来自动省略等号。因此,在配置这些插件的扩展时,需要遵循 Gradle 的标准 DSL 语法,即使用 = 来赋值。
在上面 android {…} 块中存在嵌套扩展模块 defaultConfig {…},那么这种应该如何实现呢?
① 定义一个扩展的功能类
package cn.zsk.plugin;
public class DefaultConfig { String id; }
|
② 添加到要嵌套的扩展类中国
package cn.zsk.plugin;
import org.gradle.api.Action; import org.gradle.util.internal.ConfigureUtil; import groovy.lang.Closure;
public class MyExtension { String pluginName; int version; DefaultConfig defaultConfig = new DefaultConfig();
void setDefaultConfig(Action<DefaultConfig> action) { action.execute(defaultConfig); }
void setDefaultConfig(Closure config) { ConfigureUtil.configure(config, defaultConfig); }
void defaultConfig(Action<DefaultConfig> action) { action.execute(defaultConfig); } }
|
③ 在插件中应用
package cn.zsk.plugin;
import org.gradle.api.Project; import org.gradle.api.Plugin;
public class MyPlugin implements Plugin<Project> { public void apply(Project project) {
project.getExtensions().create("myExtension", MyExtension.class);
project.getTasks().register("greeting", task -> { task.doLast(s -> System.out.println("Hello from plugin 'cn.zsk.plugin.greeting'")); });
project.getTasks().register("showExtension", task -> { task.doLast(s -> { MyExtension extension = (MyExtension) project.getExtensions().getByName("myExtension"); System.out.println("Extension : pluginName = "+extension.pluginName +" version = "+extension.version+" id = "+extension.defaultConfig.id); }); }); } }
|
这样重新上传 Maven 或者以 module 的形式 include 到项目中时,在 build.gradle 中可如下配置:
myExtension { pluginName = "zskPLugin" version = 3 defaultConfig { it.id = "1.2.3" } }
myExtension { pluginName = "zskPLugin" version = 3 defaultConfig { id = "1.2.3" } }
|
执行 ./gradlew showExtension 任务可得如下结果:
> Task :app:showExtension Extension : pluginName = zskPLugin version = 3 id = 1.2.3
|
好了我们可以来考虑另外一种情况,比如在 Android 打包 apk 时,在 build.gradle 中配置的 buildTypes 块,在这个块中,我们可以自定义扩展的属性名如比,release、debug、other… 这是怎么做到的呢? 这就不得不提 NamedDomainObjectContainer 这个属性了,这里借用这个属性来为我们的插件扩展出自定义属性名的功能。
① 定义 BuildTypes 类:
package cn.zsk.plugin;
public class BuildTypes { private String name;
private boolean signingConfig;
public BuildTypes(String name) { this.name = name; }
public String getName() { return name; }
public boolean isSigningConfig() { return signingConfig; }
public void setSigningConfig(boolean signingConfig) { this.signingConfig = signingConfig; } }
|
这里有两个必须要注意的点:
- 必须有 String name 属性,且不允许构造后修改
- 必须带有以 String name 为参数的 public 构造函数
② 将 BuildTypes 添加到扩展类中
package cn.zsk.plugin;
import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.util.internal.ConfigureUtil; import groovy.lang.Closure; import org.gradle.api.Project;
public class MyExtension { String pluginName; int version; DefaultConfig defaultConfig = new DefaultConfig();
NamedDomainObjectContainer<BuildTypes> buildTypes;
public MyExtension(Project project) { NamedDomainObjectContainer<BuildTypes> buildTypeObjs = project.container(BuildTypes.class); buildTypes = buildTypeObjs; }
void setBuildTypes(Closure config){ ConfigureUtil.configure(config, buildTypes); }
public NamedDomainObjectContainer<BuildTypes> getBuildTypes() { return buildTypes; }
void setDefaultConfig(Action<DefaultConfig> action) { action.execute(defaultConfig); }
void setDefaultConfig(Closure config) { ConfigureUtil.configure(config, defaultConfig); }
void defaultConfig(Action<DefaultConfig> action) { action.execute(defaultConfig); } }
|
③ 在插件类中使用方式
package cn.zsk.plugin;
import org.gradle.api.Project; import org.gradle.api.Plugin; import java.util.List;
public class MyPlugin implements Plugin<Project> { public void apply(Project project) {
project.getExtensions().create("myExtension", MyExtension.class);
project.getTasks().register("greeting", task -> { task.doLast(s -> System.out.println("Hello from plugin 'cn.zsk.plugin.greeting'")); });
project.getTasks().register("showExtension", task -> { task.doLast(s -> { MyExtension extension = (MyExtension) project.getExtensions().getByName("myExtension"); System.out.println("Extension : pluginName = " + extension.pluginName + " version = " + extension.version + " id = " + extension.defaultConfig.id); List<BuildTypes> buildTypes = extension.getBuildTypes().stream().toList(); for (BuildTypes types : buildTypes) { System.out.println("BuildTypes : "+types.getName() +" isSigningConfig : "+types.isSigningConfig()); } }); }); } }
|
这样当将此插件在项目中应用之后,在 build.gradle 中配置如下:
myExtension { pluginName = "zskPLugin" version = 3 defaultConfig { id = "1.2.3" } buildTypes { dev { signingConfig = false } release { signingConfig = true } } }
|
这样当执行 ./gradlew showExtension 任务时,打印信息如下:
> Task :app:showExtension Extension : pluginName = zskPLugin version = 3 id = 1.2.3 BuildTypes : dev isSigningConfig : false BuildTypes : release isSigningConfig : true
|
四、Plugins DSL
Plugins DSL 是一种专门用于配置和引用 Gradle 插件的领域特定语言。它提供了一种简介的方式来应用插件。上面我们引用插件时是采用旧的方式,需要显式的添加 classpath 等,那么当访问核心插件或者社区插件时,通过 Plugins DSL 就可以大大的简化配置过程:
① 核心插件:
② 社区插件
plugins { //apply false 语法告诉 Gradle 不要将插件应用于当前项目 id 'com.jfrog.bintray' version '1.8.5' apply false }
|
注意 Plugins DSL 方式,仅限于 Gradle 的核心插件或者上传到公共社区的插件,普通的自定义的插件只能用我们上面的旧的方式,更多可见官网Plugins DSL介绍。