博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义Gradle-Plugin 插件
阅读量:5819 次
发布时间:2019-06-18

本文共 7932 字,大约阅读时间需要 26 分钟。

自定义Gradle-Plugin 插件

给出了详细的实现步骤,笔者 将参考官方文档做一些基础介绍,额外增加一个实例:通过自定义插件修改编译后的class文件,本文按照以下三个方面进行讲解

  • 插件基础介绍
  • 三种插件的打包方式
  • 实例Demo&Debug调试

插件基础介绍

根据插件官方文档定义,插件打包了可重用的构建逻辑,可以适用不同的项目和构建。

Gradle 提供了很多官方插件,用于支持Java、Groovy等工程的构建和打包。同时也提供了自定义插件机制,让每个人都可以通过插件来实现特定的构建逻辑,并可以把这些逻辑打包起来,分享给其他人。

插件的源码可以是用Groovy、Scale、Java三种语言,笔者对Scale不熟悉,对Groovy也略知一二。Groovy用于实现构建生命周期(如Task的依赖)有关逻辑,Java用于实现核心逻辑,表现为Groovy调用Java代码

另外,还有很多项目使用Eclipse 或者Maven进行开发构建,用Java实现核心业务代码,将有利于实现快速迁移。

三种插件的实现方式

笔者编写自定义插件相关代码时,对很多GradlePluginForAndroid相关api 不熟悉,例如TransformTransformOutputProvider等,没关系,官方文档 将会是你最好的学习教程

Build Script

把插件写在build.gradle 文件中,一般用于简单的逻辑,只在改build.gradle 文件中可见,笔者常用来做原型调试。在我们指定的module build.gradle 中:

/** * 分别定义Extension1 和 Extension2 类,申明参数传递变量 */class Extension1 {    String testVariable1 = null}class Extension2 {    String testVariable2 = null}/** * 插件入口类 */class TestPlugin implements Plugin
{ @Override void apply(Project project) { //利用Extension创建e1 e2 闭包,用于接受外部传递的参数值 project.extensions.create('e1', Extension1) project.extensions.create('e2', Extension2) //创建readExtension task 执行该task 进行参数值的读取以及自定义逻辑... project.task('readExtension') << { println 'e1 = ' + project['e1'].testVariable1 println 'e2 = ' + project['e2'].testVariable2 } }}/** * 依赖我们刚刚自定义的TestPlugin,注意 使用e1 {} || e2{} 一定要放在apply plugin:TestPlugin 后面, 因为 app plugin:TestPlugin * 会执行 Plugin的apply 方法,进而利用Extension 将e1 、e2 和 Extension1 Extension2 绑定,编译器才不会报错 */apply plugin: TestPlugine1 { testVariable1 = 'testVariable1'}e2 { testVariable2 = 'testVariable2'}复制代码

相关注释说明已经在代码中简单说明,如果读者依然不熟悉或者想了解更多内容,可以在api文档中进行查阅。 然后执行readExtension task 即可

./gradlew -p moduledir readExtension --stacktrace复制代码

运行结果

buildSrc 项目

将插件源代码放在rootProjectDir/buildScr/scr/main/groovy中,只对该项目中可见,适用于逻辑较为复杂,但又不需要外部可见的插件,本文不介绍,有兴趣可以参考

独立项目

一个独立的Groovy 和Java项目,可以把这个项目打包成jar文件包,一个jar文件包还可以包含多个插件入口,可以将文件包发布到托管平台上,共其他人使用。

其实,IntelliJIEDA 开发插件要比Android Studio要方便一点,因为有对应的Groovy module模板,但如果我们了解IDEA项目文件结构,就不会受到这个局限,无非就是一个build.gradle 构建文件夹scr源码文件夹

  1. 在Android Studio中新建 Java Library module uploader(moduleName 不重要,根据实际情况定义)

  2. 修改项目文件夹

    • 移除java文件夹,因为在这个项目中用不到java代码
    • 添加Groovy文件夹,主要的代码文件放在这里
    • 添加resource文件夹,存放用于标识gradle插件的meta-data
  3. 修改build.gradle 文件

    //removed java pluginapply plugin: 'groovy'apply plugin: 'maven'repositories {	 mavenCentral()}dependencies {   compile gradleApi()//gradle sdk   compile localGroovy()//groovy sdk   compile fileTree(dir: 'libs', include: ['*.jar'])}uploadArchives {	repositories { 	   mavenDeployer {        //设置插件的GAV参数        pom.groupId = 'cn.andaction.plugin'        pom.version = '1.0.0'        //文件发布到下面目录        repository(url: uri('../repo'))   	 }   }}复制代码
  4. 建立对应文件

     ├── build.gradle  ├── libs  ├── plugin.iml  └── src      └── main          ├── groovy      │       └── cn      │           └── andaction      │               └── uploader      │                   ├── XXXPlugin.groovy      │                   └── YYYY.groovy      └── resources          └── META-INF              └── gradle-plugins                  └── uploader.properties 复制代码
    • Groovy文件夹中的类,一定要修改成.groovy 后缀,IDE才会正常识别
    • resource/META-INF/gradle-plugins这个文件夹结构是强制要求的,否则不能识别成插件

    另外,关于uploader.properties ,写过java的同学应该知道,这是一个java的properties文件,是key=value的格式,这个文件内容如下

     implementation-class=cn.andaction.uploader.XXXPlugin.groovy 复制代码

    用于指定插件入口类,其中apply plugin: '${当前配置文件名}

实例Demo

自定义gradle-plugin 并利用 类库工具修改指定编译后的class文件

笔者参考了

预备知识

  1. buid.gradle 增加类库依赖

    compile 'com.android.tools.build:gradle:3.0.1'compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'复制代码
  2. 自定义Transform

    public class PreDexTransform extends Transform {private Project project/** * 构造函数 我们将Project 保存下来备用 * @param project */PreDexTransform(Project project) {    this.project = project}.... @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {    //transformInvocation.inputs 有两种类型,一种是目录,一种是jar包 分开对其进行遍历    transformInvocation.inputs.each { TransformInput input ->        // 对类型为文件夹 的input进行遍历 :对应的class字节码文件        // 借用JavaSsist 对文件夹的class 字节码 进行修改        input.directoryInputs.each { DirectoryInput directoryInput ->        TestInject.injectDir(directoryInput.file.absolutePath, 'cn.andaction.plugin')        File des = transformInvocation.getOutputProvider().getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)            FileUtils.copyDirectory(directoryInput.file, des)        }        // 对类型为jar的input进行遍历 : 对应三方库等        input.jarInputs.each { JarInput jarInput ->            def jarName = jarInput.name            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())            if (jarName.endsWith('.jar')) {                jarName = jarName.substring(0, jarName.length() - 4) // '.jar'.length == 4            }            File dest = transformInvocation.getOutputProvider().getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)            // 将输入内容复制到输出            FileUtils.copyFile(jarInput.file, dest)        }    }    super.transform(transformInvocation)}@Overridevoid transform(Context context, Collection
    inputs, Collection
    referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { super.transform(context, inputs, referencedInputs, outputProvider, isIncremental) }}复制代码
  3. 对directoryInputs 文件夹下的class文件遍历,找到符合需要的.class 文件,通过javassit 类库对字节码文件进行修改

    TestInject.groovy

    File dir = new File(path)    classPool.appendClassPath(path)    if (dir.isDirectory()) {        dir.eachFileRecurse { File file ->            String filePath = file.path            // 这里我们指定修改TestInjectModel.class字节码,在构造函数中增加一行i will inject            if (filePath.endsWith('.class')                    && filePath.endsWith('TestInjectModel.class')) {                // 判断当前目录是否在我们的应用包里面                int index = filePath.indexOf(packageName.replace('.',File.separator))                if (index != -1) {                    int end = filePath.length() - 6 // '.class'.length = 6                    String className = filePath.substring(index, end)                            .replace('\\', '.')                            .replace('/', '.')                    // 开始修改class文件                    CtClass ctClass = classPool.getCtClass(className)                    // 拿到CtClass后可以对 class 做修改操作(addField addMethod ..)                    if (ctClass.isFrozen()) {                        ctClass.defrost()                    }                    CtConstructor[] constructors = ctClass.getDeclaredConstructors()                    if (null == constructors || constructors.length == 0) {                        // 手动创建一个构造函数                        CtConstructor constructor = new CtConstructor(new CtClass[0], ctClass)                        constructor.insertBeforeBody(injectStr) 				           //constructor.insertBefore() 会增加super(),且插入的代码在super()前面                            								  ctClass.addConstructor(constructor)                    } else {                        constructors[0].insertBeforeBody(injectStr)                    }                    ctClass.writeFile(path)                    ctClass.detach()                }            }        }    }复制代码
  4. 发布插件代码到本地

    ./gradlew -p moduleDir/ clean build uploadArchives -stacktrace复制代码
  5. 运行测试

    • build.gradle

      repositories {   maven {      url 'file:your-project-dir/repo/'	}	google()	jcenter()}dependencies {   classpath 'com.android.tools.build:gradle:3.0.1'   	classpath 'cn.andaction.plugin:uploader:1.0.0'   // NOTE: Do not place your application dependencies here; they belong   // in the individual module build.gradle files}复制代码
      apply plugin: 'uploader'复制代码
    • 修改代码

      1. 新增TestInjectModel.java,空实现
      2. app入口类onCreate方法调用new TestInjectModle()
    • 执行make project

  6. 插件调试

参考

注意,在修改插件源码后,需要重新执行uploadArchives 发布插件代码,新增/修改的代码断点才能起作用

转载地址:http://bwzdx.baihongyu.com/

你可能感兴趣的文章
ios xmpp demo
查看>>
python matplotlib 中文显示参数设置
查看>>
【ros】Create a ROS package:package dependencies报错
查看>>
HDU1576 A/B【扩展欧几里得算法】
查看>>
WebApi系列~目录
查看>>
Java访问文件夹中文件的递归遍历代码Demo
查看>>
通过容器编排和服务网格来改进Java微服务的可测性
查看>>
re:Invent解读:没想到你是这样的AWS
查看>>
PyTips 0x02 - Python 中的函数式编程
查看>>
阿里云安全肖力:安全基础建设是企业数字化转型的基石 ...
查看>>
使用《Deep Image Prior》来做图像复原
查看>>
Linux基础命令---rmdir
查看>>
iOS sqlite3(数据库)
查看>>
粤出"飞龙",打造新制造广东样本
查看>>
编玩边学获数千万元A轮融资,投资方为君联资本
查看>>
蓝图(Blueprint)详解
查看>>
Spark之SQL解析(源码阅读十)
查看>>
Android图片添加水印图片并把图片保存到文件存储
查看>>
BigDecimal 舍入模式(Rounding mode)介绍
查看>>
开源 免费 java CMS - FreeCMS1.2-标签 infoSign
查看>>