Gradle插件
Gradle插件好处:在Gradle构建过程中涉及到的通用逻辑,如果将其封装成插件,就可在多个工程项目构建中复用。
Gradle插件开发语言:Groovy、Java与Scala。不管使用哪种语言,最终都是以字节码的方式被引入Gradle。
Gradle插件开发方式
Build script:直接在Gradle文件中开发,好处是流程相对简单,因为Gradle会自动编译并引入,坏处是这是硬编码,不能灵活地在多个构建脚本中复用,适用于在单独的构建脚本中使用。如下直接在build.gradle中添加插件的例子:
apply plugin: PrinterPlugin class PrinterPlugin implements Plugin<Project> { @Override void apply(Project project) { project.task('testPrinterPluginTask') { doLast { println 'This is a task in PrinterPlugin' } } } }
命令行运行如下
buildSrc project: 直接将插件代码写在rootProjectDir/buildSrc/src/main/groovy目录下,gradle会自动编译该目录下的代码,可以直接在gradle中使用该插件。如下在标准android工程添加buildSrc project,如下示例:
插件代码如下:import org.gradle.api.Plugin import org.gradle.api.Project class BuildSrcPlugin implements Plugin<Project> { @Override void apply(Project project) { project.task('testBuildSrcPluginTask') { doLast { println 'This is a task in BuildSrcPlugin' } } } }
在gradle中使用插件:
apply plugin: BuildSrcPlugin
命令行运行如下
小结:buildSrc project好处是gradle能自动编译这个目录下的插件代码,坏处是不能在多项目中共享。相比Build script,buildSrc project适用于在一个大项目下,多个子项目间共享插件的方式。Standalone project:相比前面两种方式,在多个工程项目之间使用独立插件工程是更好的选择。独立插件之所以能突破工程项目的限制,是因为它是以jar包的方式引入到gradle构建过程。但它的缺点是流程相对复杂,主要包括编译插件包、发布插件包、引入插件包三个步骤。具体在下一小节
Groovy实现Gradle插件
目前,Gradle没有提供创建自定义Gradle插件工程的模板,需要开发者手动创建Gradle插件工程。使用Groovy开发,其Gradle插件工程必须遵循如下的目录结构:
- groovy代码必须位于xxxProject/src/main/groovy/目录下
- 提供插件属性声明文件,该文件必须位于xxxProject/src/main/resources/META-INF/gradle-plugins/xxx.properties
最简单的方式:新建一个标准Android项目来直接改造,考虑到app module要用来测试插件,所以再新建一个Android lib module来作为插件工程:
移除不用的目录,改造成如下的结构:
重写Gradle文件,下面代码就是将Groovy编译与运行时环境部署好了。
apply plugin: 'groovy' dependencies { compile gradleApi() compile localGroovy() }
编写Groovy脚本
package com.example import org.gradle.api.Plugin import org.gradle.api.Project class StandalonePlugin implements Plugin<Project> { @Override void apply(Project project) { project.task('testStandalonePluginTask') { doLast { println 'This is a task in StandalonePlugin' } } } }
至此,插件已开发完毕。接着需对插件进行配置,在/src/main/resources/META-INF/gradle-plugins/目录下新建一个名为standalone-plug.properties。注意:这个文件的名字就是在gradle中被使用名字。在这个配置文件中声明上述实现的插件,以便能被gradle使用,代码如下:
implementation-class=com.example.StandalonePlugin
本地发布与测试:maven在发包方面比较方便,可直接在本模块中的gradle中进行集成,如下代码:
apply plugin: 'maven-publish' publishing { publications { mavenJava(MavenPublication) { from components.java groupId 'com.example' artifactId 'StandalonePlugin' version '1.0.1' } } repositories { mavenLocal() } }
直接运行构建publishToMavenLocal,即会在本地仓库生成对应的插件包,其实就是一个jar包,如下
验证插件,例如在app模块中引入此插件,先需要在根目录下的build.gradle中添加本地仓库与插件的依赖,如下:
buildscript { repositories { mavenLocal() } dependencies { // 该插件的classpath与上述publishing中的参数相对应 classpath 'com.example:StandalonePlugin:1.0.1' } }
最后在app模块下build.gradle中应用插件,如下代码:
apply plugin: 'standalone-plug'
- 注意,由android工程直接改造成插件工程时,需要确认插件工程编译出来的jar有没有对应插件代码,否则无法使用该插件。
插件工作原理
应用插件:如上述例子apply plugin: ‘standalone’,等效于执行apply(plugin: ‘standalone-plug’),因此应用插件本质上是方法调用。先看这个接口方法:
/** * Applies a plugin or script, using the given options provided as a map. Does nothing if the plugin has already been applied. * <p> * The given map is applied as a series of method calls to a newly created {@link ObjectConfigurationAction}. * That is, each key in the map is expected to be the name of a method {@link ObjectConfigurationAction} and the value to be compatible arguments to that method. * * <p>The following options are available:</p> * * <ul><li>{@code from}: A script to apply. Accepts any path supported by {@link org.gradle.api.Project#uri(Object)}.</li> * * <li>{@code plugin}: The id or implementation class of the plugin to apply.</li> * * <li>{@code to}: The target delegate object or objects. The default is this plugin aware object. Use this to configure objects other than this object.</li></ul> * * @param options the options to use to configure and {@link ObjectConfigurationAction} before “executing” it */ void apply(Map<String, ?> options);
从上述接口注释来看:插件不会重复apply,提供了implementation class映射的插件会被apply(上述例子)等。即,StandalonePlugin插件的apply方法会被执行。
再看StandalonePlugin编译过后的字节码:
public class StandalonePlugin implements Plugin<Project>, GroovyObject { public StandalonePlugin() { CallSite[] var1 = $getCallSiteArray(); MetaClass var2 = this.$getStaticMetaClass(); this.metaClass = var2; } public void apply(Project project) { CallSite[] var2 = $getCallSiteArray(); class _apply_closure1 extends Closure implements GeneratedClosure { public _apply_closure1() { CallSite[] var3 = $getCallSiteArray(); super(StandalonePlugin.this, StandalonePlugin.this); } public Object doCall(Object it) { CallSite[] var2 = $getCallSiteArray(); class _closure2 extends Closure implements GeneratedClosure { public _closure2(Object _thisObject) { CallSite[] var3 = $getCallSiteArray(); super(_apply_closure1.this, _thisObject); } public Object doCall(Object it) { CallSite[] var2 = $getCallSiteArray(); return var2[0].callCurrent(this, "This is a task in StandalonePlugin"); } public Object doCall() { CallSite[] var1 = $getCallSiteArray(); return this.doCall((Object)null); } } return var2[0].callCurrent(this, new _closure2(this.getThisObject())); } public Object doCall() { CallSite[] var1 = $getCallSiteArray(); return this.doCall((Object)null); } } var2[0].call(project, "testStandalonePluginTask", new _apply_closure1()); } }
将上述字节码与源码对比:
project.task('helloPluginTask') { ... ... } // 实际对应的是下面的方法调用, 因为在DSL中允许如果最后一个参数是闭包可以省略圆括号 Task task(String var1, Closure var2); // 对应的方法调用其实就是这行: var2[0].call(project, "testStandalonePluginTask", new _apply_closure1()); // 再看doLast闭包 doLast { println 'This is a task in StandalonePlugin' } // 对应的则是_closure2 中的回调方法 public Object doCall(Object it) { CallSite[] var2 = $getCallSiteArray(); return var2[0].callCurrent(this, "This is a task in StandalonePlugin"); }
由上可知,doLast的执行实际取决于这个task的执行时机,而不是apply方法执行的时机。
Gradle插件调试总结
- 对于独立项目的Gradle插件开发流程一般如下:开发 -> 发布本地仓库 -> 集成至目标构建项目 -> 验证。经过开发实践,对于这种开发插件的方式它的调试的效率是比较低,它的瓶颈在于每次都需要将变更的插件新包发布到本地仓库。
- 推荐方式是:先使用Build script的方式直接进行开发,经过调试验证之后再迁移到独立Gradle插件的项目中。