Android Gradle Plugin插件开发——进阶

Android Gradle Plugin插件开发——基础 1.首先创建一个module 阅读详情

结合现有开源项目讲述 GradlePluginDemo

<1>Android Gradle Plugin插件开发——基础

<2>Android Gradle Plugin插件开发——进阶

<3>Android Gradle Plugin插件开发——出师

1.通过上篇讲解,已经学会了如何创建一个简单的plugin,当然这不是一个HelloWord,只是打印了另外一句话。

当使用插件com.android.application的时候,需要配置一些参数如:android,defaultConfig等,那么想给自己的插件配置参数应该如何操作了,别急,下面一一道来。
1》建立一个参数配置类

class MethodTimerExtension {
    String exclude = ""//"android,uis.com.
    boolean isMain = true//主线程
    int timeout = 100//超时时间
    boolean enableJar = true//jar包是否处理
    boolean enableLog = true
}
2》然后在上文的MethodTimerPlugin.applay中加入,此时通过project.methodTimer获取配置的值,在applay函数中暂时不能使用,在transform中就可以直接使用了
project.extensions.create("methodTimer",MethodTimerExtension)
3》在使用插件的地方配置
apply plugin: 'com.uis.methodtimer'
methodTimer{
    exclude 'interprocess.uis.com.web_demo.PermissionProxy'
    isMain false
    enableJar true
    enableLog true
    timeout 50
}
//此插件的参数配置完成,那么如何使用呢?
在上文的MethodTimerTransform.transform方法中,可以通过以下语句打印出来

println(project.methodTimer)

也可以在MethodTimerPlugin.applay中加入一个任务打印

       project.task("paramTask").doLast {
            println(project.methodTimer)
        }
2.参数获取了,那么如何进行代码注入呢?代码注入需要考虑到class文件和jar文件,对于class文件直接通过遍历目录即可;对于jar文件java提供了一个JarFile来进行遍历,此时遍历把JarEntity逐个解压到文件夹下,之后的注入的方式和之前一样,jar文件注入完成后,需要重新打包,此时使用JarOutputStream。对于没有注入的jar文件,无需重复打包,直接传递出去。以下是jar文件的解压,和目录压缩成jar的方法。
class JarZipUtil {

    /**
     * 将该jar包解压到指定目录
     * @param jarPath jar包路径
     * @param destDirPath jar包解压后路径
     * @return isExclude
     */
    static boolean unzipJar(String jarPath, String destDirPath) {
        if (jarPath.endsWith('.jar')) {
            JarFile jarFile = new JarFile(jarPath)
            jarFile.entries().each {jarEntry->
                if(!jarEntry.directory) {
                    def outFile = new File(destDirPath, jarEntry.name)
                    outFile.getParentFile().mkdirs()
                    def inputStream = jarFile.getInputStream(jarEntry)
                    def outputStream = new FileOutputStream(outFile)
                    outputStream << inputStream//这是groovy特有写法把右边输入到左右,比java简洁
                    outputStream.close()
                    inputStream.close()
                }
            }
            jarFile.close()
        }
        return false
    }

    /**
     * 重新打包jar
     * @param packagePath 目录文件
     * @param destPath jar包路径
     */
    static void zipJar(String packagePath, String destPath) {
        File dir = new File(packagePath)
        JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(destPath))
        outputStream.setLevel(Deflater.BEST_SPEED)
        dir.eachFileRecurse { file ->
            if(!file.directory) {//此处需要把windows系统下的\\替换成/,否则在后续打包遍历jar会找不到文件
                def entryName = file.getAbsolutePath().substring(packagePath.length() + 1).replace("\\","/")
                outputStream.putNextEntry(new ZipEntry(entryName))
                InputStream inputStream = new FileInputStream(file)
                outputStream << inputStream
                inputStream.close()
            }
        }
        outputStream.close()
    }
}
3.接下来要做的就是代码注入了,此时选择的是javassist进行代码注入,它是java api用起来没有障碍。

1》先要创建一个ClassPool,把transform中所有的class目录和jar解压后的目录加入进去,Javassist有几点需要注意,注入方法里不能使用泛型,虽然java支持,但是注入的代码避免,其次里面使用的类型是强类型,如果调用的方法里出现泛型,此处需要转换,下面的代码中会出现这种情况。

2》这些目录加入进来,还要加上android sdk目录,就可以进行代码注入了,否则会出现找不到类的错误

ClassPool pool = new ClassPool(true)//初始化一个ClassPool
pool.appendClassPath(project.android.bootClasspath[0].toString())//加入android sdk
pool.appendClassPath(path)//需要注入代码的class路径

//此处注入一个耗时统计的entity,保存函数开始时的时间start,一个键值key和函数信息info
CtClass ctClass = pool.makeClass("com.uis.MethodTimerEntity")//创建一个类
        CtClass string = pool.get("java.lang.String")
        ctClass.addField(CtField.make("public long start;",ctClass))//创建一个变量field
        ctClass.addField(CtField.make("public String info;",ctClass))//
        CtConstructor constructor = new CtConstructor([CtClass.longType, string, string] as CtClass[],ctClass)//定义入参
        constructor.setBody("{start = \$1;\n info = \$2+\"-\"+\$3+\" cost time = %s ms\";}")//创建构造函数
        ctClass.addConstructor(constructor)//$1$2$3表示函数的第一个,第二个,第三个入参,$0表示this指针
        ctClass.writeFile(path)//保存MethodTimerEntity到目录下,之后通过全包名使用
//此处注入2个方法,函数执行时调用startCount,结束调用endCount
//javassist不支持范型,HashMap不能加中括号,对一些类最好使用全包名引用的方式
ctClass = pool.makeClass(TAG)//TAG = com.uis.MethodTimer
        ctClass.addField(CtField.make("private static boolean isMain = ${ext.isMain};",ctClass))//创建一个变量
        ctClass.addField(CtField.make("private static int timeout = ${ext.timeout};",ctClass))
        ctClass.addField(CtField.make("private static java.util.HashMap countMap = new java.util.HashMap();",ctClass))
        ctClass.addMethod(CtNewMethod.make("public static void startCount(String key,String calssName,String method){\n" +
                "       if(!isMain || android.os.Looper.myLooper() == android.os.Looper.getMainLooper()){\n" +
                "           countMap.put(key,new com.uis.MethodTimerEntity(System.currentTimeMillis(),calssName,method));\n" +
                "       }\n"+
                "    }",ctClass))//创建一个方法method
        //fixed java.lang.VerifyError: Verifier rejected class
        //java是强类型,同时javassist不支持范型
        ctClass.addMethod(CtNewMethod.make("public static void endCount(String key){\n" +
                "        Object entity = countMap.remove(key);\n" +
                "        if((!isMain || android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) && entity != null && entity instanceof com.uis.MethodTimerEntity){\n" +
                "           com.uis.MethodTimerEntity v = (com.uis.MethodTimerEntity)entity;\n" +//强制类型转换
                "           long costtime = System.currentTimeMillis() - v.start;\n"+
                "           if(costtime > timeout){\n" +
                "               Object[] v1 = new Object[]{String.valueOf(costtime)};\n"+//String.valueOf(String,Object...)是一个泛型,String.valueOf(String,Object[]),此出需要用Object[]
"               android.util.Log.e(\"MethodTimer\",String.format(v.info,v1));\n" +
                "           }\n"+
                "       }\n"+
                "    }",ctClass))
        ctClass.writeFile(path)

3》上面通过自动生成耗时统计代码,避免使用外部的方法,让插件使用更简单。如何把代码插入到函数开始和函数结束的地方呢?请看以下内容:

void insertTimerCode(String path,String className){
//path是directory,className format is: android.app.*
        try {
            CtClass ctClass = pool.getCtClass(className)
            if (ctClass.isFrozen()) {
                ctClass.defrost()
            }
            ctClass.getDeclaredMethods().each { method ->//遍历class下的所有method
                try {//过滤空方法,和access方法
                    if (method.methodInfo.codeAttribute != null && !method.name.startsWith("access\$")) {
                        insertMehodTimer(ctClass, method)//代码注入
                    }
                } catch (Exception ex) {
                    println(TAG + "###insertMethodTimer###" + ex.message)
                }
            }
            ctClass.writeFile(path)//保存注入后的代码
            ctClass.detach()
        }catch (Exception ex){
            println(TAG + "===insertTimerCode###" + ex.message)
        }
    }

void insertMehodTimer(CtClass clas, CtMethod method) throws Exception {
        if(0 == AccessFlag.ABSTRACT.and(method.modifiers).intValue()) {//抽象方法不注入
            String className = clas.name
            String methodName = method.name
            String key = DigestUtils.md5Hex(className + methodName + method.hashCode())//给每个类的函数生成唯一键值
            String methodInfo = methodName + "(" + method.methodInfo.getLineNumber(0) + ")"
            method.insertBefore("com.uis.MethodTimer.startCount(\"${key}\",\"${className}\",\"${methodInfo}\");\n")//插入函数执行前,构造函数super后
            method.insertAfter("com.uis.MethodTimer.endCount(\"${key}\");\n")//插入到return之前
        }
    }

至此代码注入的准备工作完成,剩下的就是遍历当前目录和解压后的jar包目录注入代码,下篇继续讲解
Android Gradle 自定义Plugin实现_android plugin 而 Gradle 改良了过去 Maven、Ant 带给开发者的问题,至今也成为 Android Studio 内置的封装布署工具。 阅读详情

相关推荐

Android Gradle Plugin解析

Android Gradle Plugin的源码分析

曾志刚的博客 9471

Android Gradle 自定义Plugin实现

在 Android 项目中的 build.gradle 文件中,经常可以看见一些 plugin 声明: 上面是两种引用 gradle 插件的常用代码, 是用来构建 apk 的 gradle 插件; 是用来构建 Android Library 的 gradle 插件。Gradle 是自动化构建工具,多项目构建而设计的。通过 groovy 或 kotlin 来编写构建脚本。主要用来处理:过去 Java 开发者常用 Maven 和 Ant 等工具进行封装布署的自动化,或是两者兼用,不过这两个包彼此有优缺点:而 G

m0_56144365的博客 2791

Android Studio、JDK、AGP、Gradle、kotlin-gradle-plugin、flutter 版本兼容性问题

你从githb上clone了一个项目,本地跑的时候,各种报错。Android Studio、JDK、AGP、Gradle、kotlin-gradle-plugin 兼容性问题

薛瑄的博客 4988

Groov语法教程

Groovy是一种基础JVM(Java虚拟机)的敏捷开发语言,他结合了Python、Ruby和Smalltalk的特性,Groovy代码能够于Java代码很好的结合,也能用于扩展现有代码。由于其运行在JVM的特性,Groovy可以使用其他Java语言编写法的库。

IT飞牛 1万+

写给 Android 开发者的 Gradle 系列(三)撰写 plugin

第四行通过来获取闭包中的内容并通过反射将闭包的内容转换成一个 TestPluginExtension 对象。将会看到输出结果——

2401_84048225的博客 1048

Android Studio Gradle Plugin开发入门指南

Plugin插件入门指南1、本文目的2、Plugin简介3、Android Studio开发插件的步骤3.1 创建Java library3.2 配置plugin 项目的build.gradle文件。3.3 发布插件到本地。3.4 发使用本地插件。 1、本文目的 1、了解gradle 插件的基本知识 2、掌握插件开发的基本方法 3、根据Plugin的相关知识为后面逐步深入解析Android自动化埋点的实现原理做知识储备 4、本书参考资料: 4.1、《Gradle实战》一

菜鸟博客 2712

Groovy使用指南

目录groovy简介groovy类的基本特点在java项目中引入groovy定义变量groovy中的常见类型字符串数组 、列表 list元祖 tuple映射 | 字典 | map范围运算符 ..切片方法定义、调用闭包类的定义IO控制台输出File类文件读文件写遍历文件树说明单元测试   groovy简介 Groovy是一种基于 JVM 的编程语言,结合了 python、ruby 和 Smalltalk 的许多强大的特性,面向对象,Groovy 可以与 Java 代码很好地结合,可以直接在groo

chy-x 的博客 9858

Android Studio中Gradle插件开发详解

自定义任务是Gradle插件中非常重要的一个功能,它允许开发者封装和执行特定的构建逻辑。要创建一个自定义任务,我们首先需要定义一个任务类,该类需要继承自类。以下是一个简单的自定义任务类定义的例子:// 任务描述@get:Input// 是否跳过任务@get:Input} else {// 实际执行的任务逻辑在这段代码中,我们定义了一个名为CustomTask的抽象类,它继承自。通过Kotlin属性注入,我们为任务添加了和skip两个输入属性。

weixin_28235889的博客 1398

由浅入深的了解Groovy的基本使用

0、安装Groovy 1、解压并配置环境变量 GROOVY_HOME=F:\project\Groovy 2、输入groovy -v查看版本信息 1、了解前提 1.1、什么是Groovy? Groovy是机遇Java虚拟机的一种敏捷的动态语言,它是一种成熟的OOP(面向对象)编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言的其他特性。 1.2、于Java相比,Groovy的不同点或优势 1、Groovy完全兼容Java语法,可以做脚本也

qq_47291265的博客 7415

linux CentOS7安装VCS、Verdi、SCL

1.Linux下所需安装文件 链接:https://pan.baidu.com/s/1E_l4ARFYEnp57jlX95Bpog 提取码:ggdz ​ synopsysinstaller_v5.0 ​ scl_v2018.06 ​ vcs_mx_vO-2018.09-SP2 ​ vcs_vO-2018.09-SP2 ​ verdi-2018.9 2.Windows破解文件 ​ scl_keygen_2030 3.安装installer 打开终端,进入synopsysinstaller_

loveyelong的博客 4620

HFSS仿真的后处理结果,如何根据天线表面的电流密度分布对天线进行优化?

在HFSS仿真中,天线表面的Jsurf​是最直观、最有价值的诊断工具之一。它不仅告诉你天线“能不能工作”,还能告诉你“为什么工作”以及“哪里出了问题”。

degogmy的博客 1160

秒懂Groovy【仅限于Java开发者】

Groovy学习

良木 4899

01-SA8155P 冷启动强制进入EDL模式

冷启动EDL模式

静虚待令,有物混成 3095

Android Stduio中配置和使用Google Protobuf

网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了上述安卓开发高级工程师面试题以及答案。希望帮助大家顺利进阶为高级工程师。目前我就职于某大厂安卓高级工程师职位,在当下大环境下也想为安卓工程师出一份力,通过我的技术经验整理了面试经常问的题,答案部分是一篇文章或者几篇文章,都是我认真看过并且觉得不错才整理出来。大家知道高级工程师不会像刚入门那样被问的问题一句话两句话就能表述清楚,所以我通过过滤好文章来帮助大家理解。现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕。

2401_84123077的博客 1177

《揭秘 BME688人工智能压力湿度温度气体传感器:气体检测的多面能手》:此文为AI自动生成

BME688 是博世推出的一款具有人工智能和集成高线性度高精度压力、湿度、温度传感器的气体传感器,专为移动和联网应用开发,尺寸紧凑且低功耗。BME688 作为一款先进的传感器,在多个领域展现出卓越的性能。它采用了坚固而紧凑的 3.0 x 3.0 x 0.9 mm³ 封装,这种小巧的尺寸使其能够轻松应用于对空间要求严格的移动设备和联网应用中。同时,低功耗的特性也使得它在能源有限的环境下能够长时间稳定运行。

zheng_ruiguo的专栏 1935

Unity3D 2022版如何一键安装Newtonsoft.Json?最新Package Manager教程

本文详细介绍了在Unity3D 2022及以上版本中,如何通过Package Manager一键安装和配置Newtonsoft.Json(Json.NET)。教程涵盖了从打开Package Manager、搜索并安装正确包源,到验证安装和基础使用的完整流程,旨在帮助开发者以现代化、可靠的方式集成这一强大的JSON处理框架,提升开发效率并规避手动导入的常见问题。

lg888的博客 921

Android 插桩入门(1)

在此为大家准备了四节优质的Android高级进阶视频:架构师项目实战——全球首批Android开发者对Android架构的见解。

2401_84408404的博客 496

ICESat-2数据下载的两种方法

ICESat-2数据下载

weixin_46341898的博客 2901

Android Gradle和Gradle插件区别

引言 什么是Gradle?什么是Gradle插件? build.gradle中依赖的classpath 'com.android.tools.build:gradle:2.1.2'和gradle-wrapper.properties中的distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip有什

jinrall的专栏 1万+

UNet 改进(22):结合Transformer结构

保留了UNet的经典编码器-解码器结构在瓶颈层使用Transformer替代传统的卷积层采用跳跃连接保持空间细节信息使用双卷积块(DoubleConv)作为基础构建模块。

2401_82355416的博客 775

PR工具timing report中setup time的计算过程

然后去pr工具里报告report_delay_calculation/reportDelayCalculation -from related_pin -to constraint_pin。这里constraint pin就是D pin (D0 D1 D2 D3),related pin就是 CK pin。我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?

m0_61544122的博客 458

Android Gradle Plugin、Gradle、Android Studio版本关系

Android Studio 构建系统以 Gradle 为基础,并且 Android Gradle 插件 (AGP) 添加了几项专用于构建 Android 应用的功能。所要求的版本,可能会导致意外问题。我们建议您使用最新的预览版 Android Studio 和 AGP 来处理以预览版 Android OS 为目标平台的项目。如果使用的 Android Studio 或 AGP 版本低于项目的。如果您的项目不受某个特定版本的 Android Studio 支持,您仍然可以使用。

蓝色的天空的博客 4711

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Android Gradle - Gradle、Gradle plugin 基础认知

起由 :因为开发中build项目失败,提示了Gradle相关问题;所以在好奇心的驱使之下,抽时间好好梳理了一下Gradle方面的知识 - -~

Android、前端、小程序、后端 5864

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Android Gradle与Gradle Plugin关系

目录 Gradle是什么? Gradle插件又是什么? 问题背景: 明确概念: 关系依赖: 三者版本对应关系: Gradle是什么? Gradle其实就是一个构建项目的工具,即把一堆堆的目录、文件按照一定的规则关联起来,形成一个项目的自动化构建工具。 Gradle插件又是什么? Gradle插件是针对Gradle发行版和Android SDK Build Tools封装的一个工...

zcmain的专栏 5275

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

一篇学会Android Gradle插件开发

自定义插件的时候经常会有这种自定义配置的需求,通过自定义的配置可以让我们的插件提供更丰富的能力。这些配置就是通过扩展插件来的。增加定义了一个SubExtension类,然后在YiRanPluginExtension实例化的时候加到ExtensionContainer中。类嵌套的话,不能写成内部类,不然编译识别不过。

ChenYiRan123456的博客 2004
上一篇: Android Gradle Plugin插件开发——基础
下一篇: 侧滑弹性+加载更多ViewPager
Luiing
博客等级 码龄17年 9粉丝 14原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值