编译插桩操作字节码(ASM版本)

2020-05-25  本文已影响0人  卡路fly

来源:编译插桩操纵字节码,实现不可能完成的任务

编译插桩

编译插桩就是在代码编译期间修改已有的代码或者生成新代码。实际上,我们项目中经常用到的 Dagger、ButterKnife 甚至是 Kotlin 语言,它们都用到了编译插桩的技术。

Android 项目中 .java 文件的编译过程

我们可以在 1、2 两处对代码进行改造。

  1. 在 .java 文件编译成 .class 文件时,APT、AndroidAnnotation 等就是在此处触发代码生成。

  2. 在 .class 文件进一步优化成 .dex 文件时,也就是直接操作字节码文件,也是本课时主要介绍的内容。这种方式功能更加强大,应用场景也更多。但是门槛比较高,需要对字节码有一定的理解。

使用第二种实现方式

编译插桩实现功能

插桩工具

ASM 实现在每一个 Activity 打开时输出相应的 log 日志

思路

  1. 遍历项目中所有的 .class 文件
    众所周知,Android Studio 使用 Gradle 编译项目中的 .java 文件,并且从 Gradle1.5.0 之后,我们可以自己定义 Transform,来获取所有 .class 文件引用。但是 Transform 的使用需要依赖 Gradle Plugin。因此我们第一步需要创建一个单独的 Gradle Plugin,并在 Gradle Plugin 中使用自定义 Transform 找出所有的 .class 文件。

  2. 遍历到目标 .class 文件 (Activity)之后,通过 ASM 动态注入需要被插入的字节码
    过滤出目标 Activity 文件,并在目标 Activity 文件的 onCreate 方法中,通过 ASM 插入相应的 log 日志字节码。

Transform

Transform 可以被看作是 Gradle 在编译项目时的一个 task,在 .class 文件转换成 .dex 的流程中会执行这些 task,对所有的 .class 文件(可包括第三方库的 .class)进行转换,转换的逻辑定义在 Transform 的 transform 方法中。

实际上平时我们在 build.gradle 中常用的功能都是通过 Transform 实现的,比如混淆(proguard)、分包(multi-dex)、jar 包合并(jarMerge)。

getName:

设置我们自定义的 Transform 对应的 Task 名称。Gradle 在编译的时候,会将这个名称显示在控制台上。比如:Task :app:transformClassesWithXXXForDebug。

getInputType:

在项目中会有各种各样格式的文件,通过 getInputType 可以设置 LifeCycleTransform 接收的文件类型,此方法返回的类型是 Set<QualifiedContent.ContentType> 集合。

ContentType 有以下 2 种取值。

getScopes()

这个方法规定自定义 Transform 检索的范围,具体有以下几种取值:


isIncremental()

表示当前 Transform 是否支持增量编译,我们不需要增量编译,所以直接返回 false 即可。

transform()

在 自定义Transform 中最重要的方法就是 transform()。在这个方法中,可以获取到两个数据的流向。

ASM

ASM 是一套开源框架,其中几个常用的 API 如下:

具体实现

  1. 创建类 LifeCyclePlugin.groovy 文件。在 LifeCyclePlugin 中重写 apply 方法,实现插件逻辑
  2. 修改 asm_lifecycle_plugin module 的 build.gradle
  3. 创建 properties 文件
  4. 自定义Transform,实现transform方法
  5. 将自定义的 LifeCycleTransform 注册到 Gradle 插件中
  6. 使用 ASM,插入字节码到 Activity 文件
上一篇 下一篇

猜你喜欢

热点阅读