调试研究Shadow对字节码编辑的正确姿式

Shadow是经过字节码编辑技术向插件插入中间层,完成插件技术的核心工做的。因此,有必要给新接触字节码编辑技术的同窗分享一下研究这项技术的入门姿式。java

构建过程介绍

Android 官方的构建过程提供了名为TransForm的API,详见这里 。这个API容许第三方插件在class转换成dex以前编辑class。Shadow就写了这样一个第三方API,就是在插件的build.gradle中添加的apply plugin: 'com.tencent.shadow.plugin'android

关于Transform,要注意,一次构建容许有多个Transform。可是构建系统不保证这些Transform的顺序。就是多个Transform谁先执行,谁后执行,你在设计Transform时是不能假定的。Shadow的Transform会修改一些类的父类,或者一些类的名字。若是其余Transform试图以类型或名字查找类的话,那它在Shadow的Transform前执行和以后执行的效果就不同了。对于这种状况,建议修改Shadow的Transform,将两个Transform写在同一个Plugin中,这样在Plugin内部就能够控制顺序了。编程

插件应用了Shadow的Plugin后,在构建过程当中就会调用到com.tencent.shadow.core.gradle.ShadowPlugin#apply。它是经过projects/sdk/core/gradle-plugin/build.gradle中的这段配置找到目标类的。api

gradlePlugin {
    plugins {
        shadow {
            id = "com.tencent.shadow.plugin"
            implementationClass = "com.tencent.shadow.core.gradle.ShadowPlugin"
        }
    }
}
复制代码

apply中,咱们经过这段代码向构建系统注册了咱们的Transform程序。bash

plugin.extension.registerTransform(ShadowTransform(...))
复制代码

会执行一个Transform任务,好比transformClassesWithShadowTransformForDebug。这个任务会调用ShadowTransform对象的父类方法com.tencent.shadow.core.transform_kit.ClassTransform#transform,进入咱们Transform的真正入口app

Gradle任务的增量构建检测机制只检查任务的输入和输出是否发生了变化。这在通常状况下显然是合理的。输入没变,输出都在,确定是不用从新执行任务的。可是在Shadow的开发中,Transform这个任务也是咱们源码的一部分。所以在这种默认机制下,插件原始字节码做为输入,插件编辑后字节码做为输出,若是只修改Transform的逻辑,好比将类A重命名为B的逻辑改为A重命名为C。因为输入没变,输出都在,这个更新的Transform逻辑就不会执行了。所以在com.tencent.shadow.core.transform_kit.ClassTransform#getSecondaryFiles中,咱们将Transform程序自己也作为了Transform程序的附带输入。这样Transform任务就能判断出输入由于Transform程序自己变化而变化了,于是从新执行Transform任务。这样我才达到了编辑Transform源码,也能一键运行的效果。学习

查看Transform先后的插件字节码

咱们在开发中把sample-plugin作成一个aar库,而后分别包装成正常安装的sample-normal-app和插件sample-plugin-app,就是为了能比较方便的同时查看应用Transform先后的字节码差别。gradle

在Android Studio中直接双击打开一个apk文件(下图中1),而后在详情中选中dex文件(下图中2),就能显示出dex中的类。点击下图中3指向的按钮,隐藏掉没有真正打包在当前dex中的类。而后选择一个类(下图4),按右键,选择Show Bytecodeui

好比查看正常安装的apk的字节码,以下:spa

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Landroid/app/Activity;
.source "TestActivityReCreate.java"

# direct methods
.method public constructor <init>()V
    .registers 1

    .line 31
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method
复制代码

再查看插件apk中相同类的字节码,以下:

.class public Lcom/tencent/shadow/sample/plugin/app/lib/usecases/activity/TestActivityReCreate;
.super Lcom/tencent/shadow/core/runtime/ShadowActivity;
.source "TestActivityReCreate.java"

# direct methods
.method public constructor <init>()V
    .registers 1

    .line 31
    invoke-direct {p0}, Lcom/tencent/shadow/core/runtime/ShadowActivity;-><init>()V

    return-void
.end method
复制代码

能够对比出来,第二行的.super发生了变化,表示这个类的父类被改变了。下面方法中的invoke-direct调用的方法也变化了。

修改Transform程序

好比前面例子中涉及到的系统Activity替换成ShadowActivity,就是在类com.tencent.shadow.core.transform.specific.ActivityTransform中实现的。你们能够直接修改这个类的源码,而后从新运行sample-host就能够看到效果了。

Shadow对全部字节码的修改逻辑都放在了com.tencent.shadow.core.transform.specific包中。

Shadow的transform-kit是Shadow在作字节码编辑工做时沉淀的通用代码,应该能够直接用在Android上进行任何字节码编辑工做。好比直接接入业务,经过实现SpecificTransform进行AOP编程。

transform-kit的设计

transform-kit的设计确实没有久经考验。但愿你们用起来以后可以开源共建的改进它。

它目前主要解决了这样几个问题。

  1. ClassTransform解决如何将App本身的类和依赖jar包,输入到内存中待编辑,而后在编辑后再输出到文件。
  2. JavassistTransform解决如何将ClassTransform和Javassist联系起来。
  3. AbstractTransform负责组织Transform的抽象过程,就是例如先setup后fire这样的顺序,在这里固定下来。
  4. AbstractTransformManagerSpecificTransformTransformStep,联合起来组织各个平行不相关的Transform。规定每一个SpecificTransform由多个TransformStep构成。先配置Step,再统一执行。这个设计是为了能让每一个Transform串行工做,每一个Transform工做时都能处理全部的类。而不是每一个类按顺序通过全部的Transform,Shadow最先的代码就犯了这个错误。每一个类去作完全部Transform,可能再下一个类引用其余类时出现其余类已经处理过或者没有处理过两种状况。
  5. AndroidClassPoolBuilder完成如何将Android SDK的类导入Javassist中使用。

先分享这么多,相信一部分人了解这些就能够经过查阅Javassist的文档学习Javassist的用法,而后查看com.tencent.shadow.core.transform.specific包中已有的代码,就能完全学会如何扩展Shadow的功能,也能利用Shadow的transform-kit在其余应用中进行AOP编程了。

相关文章
相关标签/搜索