Gradle插件开发(2) - extensions和Task

经过前边两篇介绍,我们了解了Gradle的基础知识和如何写一个自己的插件,我们今天,开始实战,搞点有趣的东西。

准备

今天前面的介绍,我们是可以实现自己的自定义task,当时android在构建是一个很琐碎的过程,之前的各个环节都是task,为了让开发人员更少也更好写业务代码,后来出了一套Transform API

Transform

Transform 介绍

Transform是特意为Android打造的,按照官网的解释如下:

Starting with 1.5.0-beta1, the Gradle plugin includes a Transform API allowing 3rd party plugins to manipulate compiled class files before they are converted to dex files. (The API existed in 1.4.0-beta2 but it's been completely revamped in 1.5.0-beta1)

The goal of this API is to simplify injecting custom class manipulations without having to deal with tasks, and to offer more flexibility on what is manipulated. The internal code processing (jacoco, progard, multi-dex) have all moved to this new mechanism already in 1.5.0-beta1.
Note: this applies only to the javac/dx code path. Jack does not use this API at the moment.

简单解释下就是:

  1. Transforms是重新引入的,主要作用在对class的处理上,也是在生成dex文件前。
  2. Transforms有很强大功能,避免了大家使用task,内部可以处理jacoco, progard, multi-dex等过程。

在Android studio下我们若是执行./gradlew tasks就会发现很多以那么Transforms开头的task。

Transform API

我们可以注册多个transform,这个类似于task流式关系。 这写API可以参考如何理解 Transform API,可以认为transform是码头搬运的师父,你加入一个人,不影响个整体的码头的云端,前提是你要按照码头的规矩办事。 那么transform遵循码头的那些规矩呢?当然就是Transforms的API了,你要严格按照这个API定义每一步。 那么Transforms 有哪些API呢:

public class XXXTransform extends Transform {@Overridepublic String getName() {return null;}@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return null;}@Overridepublic Set<? super QualifiedContent.Scope> getScopes() {return null;}@Overridepublic boolean isIncremental() {return false;}@Overridepublic void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {super.transform(transformInvocation);}
}
复制代码

来解释下每个函数的作用:

  1. getName定义transform的名字,便于程序区分
  2. getInputTypes拦截输入的流的类型,哪些它会拦截呢?
public class TransformManager extends FilterableStreamCollection {public static final Set<ContentType> CONTENT_CLASS;public static final Set<ContentType> CONTENT_JARS;public static final Set<ContentType> CONTENT_RESOURCES;public static final Set<ContentType> CONTENT_NATIVE_LIBS;public static final Set<ContentType> CONTENT_DEX;public static final Set<ContentType> CONTENT_JACK;}
复制代码
  1. getScopes是指transform的作用域,可以管理子模块还是其他的,具体参考TransformManager
public static final Set<Scope> SCOPE_FULL_PROJECT = Sets.immutableEnumSet(Scope.PROJECT,Scope.PROJECT_LOCAL_DEPS,Scope.SUB_PROJECTS,Scope.SUB_PROJECTS_LOCAL_DEPS,Scope.EXTERNAL_LIBRARIES);public static final Set<QualifiedContent.ScopeType> SCOPE_FULL_INSTANT_RUN_PROJECT =new ImmutableSet.Builder<QualifiedContent.ScopeType>().addAll(SCOPE_FULL_PROJECT).add(InternalScope.MAIN_SPLIT).build();public static final Set<Scope> SCOPE_FULL_LIBRARY = Sets.immutableEnumSet(Scope.PROJECT,Scope.PROJECT_LOCAL_DEPS);复制代码
  1. isIncremental:用于指明是否是增量构建
  2. transform(TransformInvocation transformInvocation)是整个类的核心,inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错。

注册Transform

注册一个Transform到项目里,需要通过Plugin实现,

public class TranPlugin implements Plugin<Project> {void apply(Project project) {System.out.println("------------------开始----------------------");//AppExtension就是build.gradle中android{...}这一块def android = project.extensions.getByType(AppExtension)//注册自己的Transformdef classTransform = new ClassTransform(project);android.registerTransform(classTransform);System.out.println("------------------结束了吗----------------------");}}
复制代码

实现自己的Transform

public class ClassTransform extends Transform {private Project mProject;public ClassTransform(Project p) {this.mProject = p;}/** transform的名称* transformClassesWithMyClassTransformForDebug 运行时的名字*/@Overridepublic String getName() {return "MyClassTransform";}//需要处理的数据类型,有两种枚举类型//CLASSES和RESOURCES,CLASSES代表处理的java的class文件,RESOURCES代表要处理java的资源@Overridepublic Set<QualifiedContent.ContentType> getInputTypes() {return TransformManager.CONTENT_CLASS;}/*** 指Transform要操作内容的范围,官方文档Scope有7种类型:*  EXTERNAL_LIBRARIES        只有外部库*  PROJECT                       只有项目内容*  PROJECT_LOCAL_DEPS            只有项目的本地依赖(本地jar)*  PROVIDED_ONLY                 只提供本地或远程依赖项*  SUB_PROJECTS              只有子项目。*  SUB_PROJECTS_LOCAL_DEPS   只有子项目的本地依赖项(本地jar)。*  TESTED_CODE                   由当前变量(包括依赖项)测试的代码*/@Overridepublic Set<QualifiedContent.Scope> getScopes() {return TransformManager.SCOPE_FULL_PROJECT;}/*** 指明当前Transform是否支持增量编译*/@Overridepublic boolean isIncremental() {return false;}/*** Transform中的核心方法* inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。* outputProvider 获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错*/@Overridepublic void transform(Context context,Collection<TransformInput> inputs,Collection<TransformInput> referencedInputs,TransformOutputProvider outputProvider,boolean isIncremental) throws IOException, TransformException, InterruptedException {System.out.println("----------------进入transform了--------------")System.out.println("--------------结束transform了----------------")}}
复制代码

若这样,什么都不写,是我们的transform会报错,我们可以将遍历input文件,:

//遍历inputinputs.each {TransformInput input ->//遍历文件夹input.directoryInputs.each {DirectoryInput directoryInput ->// 获取output目录def dest = outputProvider.getContentLocation(directoryInput.name,directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)println(directoryInput.file.getPath() + " ---> " + dest.toPath())// 将input的目录复制到output指定目录FileUtils.copyDirectory(directoryInput.file, dest)}遍历jar文件 对jar不操作,但是要输出到out路径input.jarInputs.each {JarInput jarInput ->// 重命名输出文件(同目录copyFile会冲突)def jarName = jarInput.nameprintln("jar = " + jarInput.file.getAbsolutePath())def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())if (jarName.endsWith(".jar")) {jarName = jarName.substring(0, jarName.length() - 4)}def dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR)FileUtils.copyFile(jarInput.file, dest)}}
复制代码

可以看到我们什么都没做,就是copy,不过copy文件是需要重新命名一个文件。
这样就可以将码头搬运过程中,从上一个工人人的东西,直接传给了下一个工人,只是有少些的改动。 那我们若是想在其中进行少些的改动呢?这就需要用到一个神奇的工具javassist

Javassist

Javaassist可以用 Javassist 改变 Java 类的字节码,而无需真正了解关于字节码或者 Java 虚拟机(Java virtual machine JVM)结构的任何内容,这样他就可以直接修改class文件的字节码。

Javassist 基础

Javassist 使您可以检查、编辑以及创建 Java 二进制类。检查方面基本上与通过 Reflection API 直接在 Java 中进行的一样,但是当想要修改类而不只是执行它们时,则另一种访问这些信息的方法就很有用了。这是因为 JVM 设计上并没有提供在类装载到 JVM 中后访问原始类数据的任何方法,这项工作需要在 JVM 之外完成。

Javassist 使用 javassist.ClassPool 类跟踪和控制所操作的类。这个类的工作方式与 JVM 类装载器非常相似,但是有一个重要的区别是它不是将装载的、要执行的类作为应用程序的一部分链接,类池使所装载的类可以通过 Javassist API 作为数据使用。

装载到类池中的类由 javassist.CtClass 实例表示。与标准的 Java java.lang.Class 类一样, CtClass 提供了检查类数据(如字段和方法)的方法。不过,这只是 CtClass 的部分内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist 没有提供删除一个类中字段、方法或者构造函数的任何方法。

字段、方法和构造函数分别由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。

读写

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("me.cyning.cc.Rectangle");
cc.setSuperclass(pool.get("me.cyning.cc.Point"));
cc.writeFile();
复制代码

这样就将test.Rectangle的父类设置为test.Point,其中 pool.get("test.Rectangle")就是用默认的ClassPool来装寨对应的class,并且可以使用装载的CtClass.

调用 writeFile() 后,这项修改会被写入原始类文件. 怎么验证呢?

 Class rc = cc.toClass();System.out.println(rc.getSuperclass());
复制代码

函数

我们可以寨函数的前面和后面插入我们的值,如何做呢?

public static void main(String[] args) throws Exception {ClassPool cp = ClassPool.getDefault();CtClass cc = cp.get("me.cyning.Hello");CtMethod m = cc.getDeclaredMethod("say");m.insertBefore("{ System.out.println(\"before Hello.say():\"); }");m.insertAfter("{ System.out.println(\"after Hello.say():\"); }");Class c = cc.toClass();Hello h = (Hello)c.newInstance();h.say();}public class Hello {public void say() {System.out.println("Hello");}
}复制代码

先将me.cyning.Helloload到Javassist的ClassPool中,获取到me.cyning.Hello类中的say方法,可以在方法开始和结尾来加入函数。 是不是有点意思。

打点统计

于是我们可以在我们类中直接使用Javassist来修改我们的class字节码。 接着transform中的transform方法来处理,所有的class到transform方法中都是以class文件存在的,所以拦截文件夹。

只需要在适当的方法里注入如下代码:

  ``````````````````````println("方法名 = " + mMethod.getName())println("返回类型 = " + mMethod.getReturnType().getName())
//                        println("参数类型 = " + mMethod.getParameterTypes())// 开始时间mMethod.addLocalVariable("startMs", CtClass.longType);mMethod.insertBefore("startMs = System.currentTimeMillis();");String body = "{" +"final long endMs = System.currentTimeMillis();" +"System.out.println(\"Executed in ms: [" + className + "," + mMethod.getName() +"] ---> \" + (endMs-startMs));}"println(body)mMethod.insertAfter(body)
复制代码

找到对应的class的看下,生效:

最后的代码: github.com/ownwell/Gra…

参考文章

Transform API Android Plugin Transform 初探 Gradle通过Transform API实现代码注入 深入理解 Android 之 Gradle Tutorial 1 Android ASM 插桩初步实现 Android热修复技术——QQ空间补丁方案解析(3)
网易乐得-Android AOP之字节码插桩

转载于:https://juejin.im/post/5cffc1cee51d4556bc066f4a

Gradle插件开发- 无侵入的函数运行时间统计的实现相关推荐

  1. java 无侵入监控_MyPerf4J 一个高性能、无侵入的Java性能监控和统计工具

    MyPerf4J 一个针对高并发.低延迟应用设计的高性能且无侵入的实时Java性能监控和统计工具. 受 perf4j 和 TProfiler启发而来. MyPerf4J具有以下几个特性: 无侵入: 采 ...

  2. linux下统计程序/函数运行时间

    一. 使用time 命令 例如编译一个hello.c文件 #gcc hello.c -o hello 生成了hello可执行文件,此时统计该程序的运行时间便可以使用如下命令 #time ./hello ...

  3. Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed

    阿里巴巴无线事业部近期开源的Android平台下的无侵入运行期AOP框架Dexposed,该框架基于AOP思想,支持经典的AOP使用场景.可应用于日志记录,性能统计,安全控制.事务处理.异常处理等方面 ...

  4. Android免Root无侵入AOP框架Dexposed

    Dexposed框架是阿里巴巴无线事业部近期开源的一款在Android平台下的免Root无侵入运行期AOP框架,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事 ...

  5. spring学习笔记(14)引介增强详解:定时器实例:无侵入式动态增强类功能

    引介增强实例需求 在前面我们已经提到了前置.后置.环绕.最终.异常等增强形式,它们的增强对象都是针对方法级别的,而引介增强,则是对类级别的增强,我们可以通过引介增强为目标类添加新的属性和方法,更为诱人 ...

  6. Dexposed:Android平台免Root无侵入AOP框架

    本文来自阿里巴巴技术协会(ATA) 本文首发于 http://www.infoq.com/cn/news/2015/07/dexposed 近日,阿里巴巴无线事业部推出首个重量级Android开源项目 ...

  7. 网站底部运行时间的php代码,网站底部运行时间统计代码

    也许您和我一样,想在自己站点底部或者任意位置添加一个运行时间统计的代码,对我来说这是一个特殊的日子,值得留恋,值得铭记. 在这里我也收集并测试了部分,能有效的显示本站已运行N天,接下来上干货吧. js ...

  8. Android Gradle插件开发基础

    什么是Gradle 在Gradle官方文档上是这么描述的: Gradle 是一种开源构建自动化工具,其设计足够灵活,几乎可以构建任何类型的软件. Gradle 允许您构建任何软件,因为它对您尝试构建的 ...

  9. 【笔记】《由浅入深SCF无服务器云函数实践》

    <由浅入深SCF无服务器云函数实践> 学习笔记 来源:DockOne技术交流群分享 主办方:dockone.io 地点:DockOne技术交流群 时间:2017年12月26日晚8:30 分 ...

  10. 架构系列---消息点击率翻倍的背后——闲鱼无侵入可扩展IFTTT系统

    面临问题 在闲鱼生态里,用户之间会有很多种关系.其中大部分关系是由买家触发,联系到卖家,比如买家通过搜索.收藏.聊天等动作与卖家产生联系:另外一部分是平台与用户之间的关系.对这些关系分析之后我们发现这 ...

最新文章

  1. Dictionary解析json,里面的数组放进list,并绑定到DataGridView指定列
  2. 7.22 校内模拟赛
  3. C语言经典例20-小球反弹高度问题
  4. python- 进阶 与flask的搭配使用---定时任务框架APScheduler学习详解
  5. SBuild 0.1.4 发布,基于 Scala 的构建系统
  6. ESFramework介绍之(23)―― AgileTcp
  7. ACM算法 -- 数论 -- 开灯关灯问题(数论,整数分解,因子个数,公式推导)
  8. Chrome查看cookie
  9. 考清华计算机研究生数学看什么,一位考上清华计算机研究生的悲壮历程(数学考了满分).doc...
  10. 拒绝低效办公,9个超实用职场必备国产软件推荐
  11. 3.15 曝光:40 亿 AI 骚扰电话和 11 家合谋者
  12. 【HANA系列】SAP HANA XS使用Odata标志全解析
  13. 谭浩强 C语言程序设计第五版 第六章 习题 答案
  14. 电脑常用的十款工具软件
  15. 工作流(activiti7)-简单的介绍和使用(二)
  16. android 智能手环应用,戴图智能手环app(健康手环应用) 1.7.8安卓版
  17. iPhone 小技巧/实用功能
  18. 新手入坑:strapi官网教程的简单示例学习
  19. 如何将二维码分解成链接?二维码解码在线怎么操作?
  20. SQLsever数据库实例是啥子

热门文章

  1. Django Step by Step 2010版(基于Django 1.1.1) 第五讲
  2. 更新CocoaPods1.1.0碰到的问题及知识点
  3. [nRF51822] 1、一个简单的nRF51822驱动的天马4线SPI-1.77寸LCD彩屏DEMO
  4. 十个个必装的火狐插件
  5. Iptables 中文 man 文档
  6. Ubuntu 16.04下安装激活pycharm 2018.3版本
  7. H264 SPS分析
  8. 用gdb调试动态链接库
  9. Linux内核多线程(二)
  10. B - 吉哥系列故事——完美队形II HDU - 4513 (马拉车)