自定义Gradle插件

Gradle插件
  1. Gradle插件好处:在Gradle构建过程中涉及到的通用逻辑,如果将其封装成插件,就可在多个工程项目构建中复用。

  2. Gradle插件开发语言:Groovy、Java与Scala。不管使用哪种语言,最终都是以字节码的方式被引入Gradle。

Gradle插件开发方式
  1. 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'
                }
            }
        }
    }

    命令行运行如下
    这里写图片描述

  2. 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适用于在一个大项目下,多个子项目间共享插件的方式。

  3. Standalone project:相比前面两种方式,在多个工程项目之间使用独立插件工程是更好的选择。独立插件之所以能突破工程项目的限制,是因为它是以jar包的方式引入到gradle构建过程。但它的缺点是流程相对复杂,主要包括编译插件包、发布插件包、引入插件包三个步骤。具体在下一小节

Groovy实现Gradle插件
  1. 目前,Gradle没有提供创建自定义Gradle插件工程的模板,需要开发者手动创建Gradle插件工程。使用Groovy开发,其Gradle插件工程必须遵循如下的目录结构:

    • groovy代码必须位于xxxProject/src/main/groovy/目录下
    • 提供插件属性声明文件,该文件必须位于xxxProject/src/main/resources/META-INF/gradle-plugins/xxx.properties
  2. 最简单的方式:新建一个标准Android项目来直接改造,考虑到app module要用来测试插件,所以再新建一个Android lib module来作为插件工程:
    这里写图片描述

  3. 移除不用的目录,改造成如下的结构:
    这里写图片描述

  4. 重写Gradle文件,下面代码就是将Groovy编译与运行时环境部署好了。

    apply plugin: 'groovy'
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
  5. 编写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'
                }
            }
        }
    }
  6. 至此,插件已开发完毕。接着需对插件进行配置,在/src/main/resources/META-INF/gradle-plugins/目录下新建一个名为standalone-plug.properties。注意:这个文件的名字就是在gradle中被使用名字。在这个配置文件中声明上述实现的插件,以便能被gradle使用,代码如下:

    implementation-class=com.example.StandalonePlugin

    这里写图片描述

  7. 本地发布与测试:maven在发包方面比较方便,可直接在本模块中的gradle中进行集成,如下代码:

    apply plugin: 'maven-publish'
    
    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
    
                groupId 'com.example'
                artifactId 'StandalonePlugin'
                version '1.0.1'
            }
        }
    
        repositories {
            mavenLocal()
        }
    }
  8. 直接运行构建publishToMavenLocal,即会在本地仓库生成对应的插件包,其实就是一个jar包,如下
    这里写图片描述
    这里写图片描述

  9. 验证插件,例如在app模块中引入此插件,先需要在根目录下的build.gradle中添加本地仓库与插件的依赖,如下:

    buildscript {
        repositories {
            mavenLocal()
        }
        dependencies {
            // 该插件的classpath与上述publishing中的参数相对应
            classpath 'com.example:StandalonePlugin:1.0.1'
        }
    }
  10. 最后在app模块下build.gradle中应用插件,如下代码:

    apply plugin: 'standalone-plug'
  11. 注意,由android工程直接改造成插件工程时,需要确认插件工程编译出来的jar有没有对应插件代码,否则无法使用该插件。
    这里写图片描述
    这里写图片描述
插件工作原理
  1. 应用插件:如上述例子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);
  2. 从上述接口注释来看:插件不会重复apply,提供了implementation class映射的插件会被apply(上述例子)等。即,StandalonePlugin插件的apply方法会被执行。

  3. 再看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());
        }
    }
  4. 将上述字节码与源码对比:

    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");
    }
  5. 由上可知,doLast的执行实际取决于这个task的执行时机,而不是apply方法执行的时机

Gradle插件调试总结
  1. 对于独立项目的Gradle插件开发流程一般如下:开发 -> 发布本地仓库 -> 集成至目标构建项目 -> 验证。经过开发实践,对于这种开发插件的方式它的调试的效率是比较低,它的瓶颈在于每次都需要将变更的插件新包发布到本地仓库。
  2. 推荐方式是:先使用Build script的方式直接进行开发,经过调试验证之后再迁移到独立Gradle插件的项目中。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值