Transform Api + ASM 插桩(实现示例)

基础知识准备

1.如何实现gradle插件
2.什么是Transform以及如何使用?
3.什么是ASM以及如何使用?

下面请阅读:https://www.jianshu.com/p/16ed4d233fd1 (大佬树下好乘凉 ,上来就是一顿神操作)

注:以下场景只是入门示例 希望通过这几个示例能够触类旁通 了解一些ASM的基础应用和API调用


以下为一些场景的使用

在下面示例中的字节码中会有一些如下指令:
LINENUMBER 34 L0
L0 L1 L2 L3
遇见 LINENUMBER 直接忽略它的存在
L0 L1 一般方法内添加代码块的情况下才会使用 例如if判断 trycatch等, 无代码块的时候忽略即可,具体使用看示例即可

1.方法计时统计
//方法计时
public fun setContent(){
     val startTime = System.currentTimeMillis()
     //......   
     //doSomething
     //......
     val endTime = System.currentTimeMillis()
     Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
 }

//上述统计代码转化为字节码后的内容
LINENUMBER 34 L0
 INVOKESTATIC java/lang/System.currentTimeMillis ()J
 LSTORE 1
L1
 LINENUMBER 35 L1
 INVOKESTATIC java/lang/System.currentTimeMillis ()J
 LSTORE 3
L2
 LINENUMBER 36 L2
 LDC "TAG"
 NEW java/lang/StringBuilder
 DUP
 INVOKESPECIAL java/lang/StringBuilder.<init> ()V
 LDC "\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a"
 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
 LLOAD 3
 LLOAD 1
 LSUB
 INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
 INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
 INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
 POP
L3
//MethodVisitor具体插入细节
class TransformTimeMethodVisitor extends MethodVisitor{

    TransformTimeMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv)
    }

    @Override
    void visitCode() {
        super.visitCode()
        //方法前插入
        //val startTime = System.currentTimeMillis()
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
        mv.visitVarInsn(Opcodes.LSTORE,1)
    }


    @Override
    void visitInsn(int opcode) {
        //方法结束处
        if (opcode == Opcodes.RETURN){
            //val endTime = System.currentTimeMillis()
            mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/System","currentTimeMillis","()J",false)
            mv.visitVarInsn(Opcodes.LSTORE,3)

            //Log.e("TAG" ,"执行setContent方式耗时:${endTime - startTime}")
            mv.visitLdcInsn("TAG")
            mv.visitTypeInsn(Opcodes.NEW,"java/lang/StringBuilder")
            mv.visitInsn(Opcodes.DUP)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder","<init>","()V",false)
            mv.visitLdcInsn("\u6267\u884csetContent\u65b9\u5f0f\u8017\u65f6\uff1a")
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false)
            mv.visitVarInsn(Opcodes.LLOAD,3)
            mv.visitVarInsn(Opcodes.LLOAD,1)
            mv.visitInsn(Opcodes.LSUB)
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(J)Ljava/lang/StringBuilder;",false)
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","toString","()Ljava/lang/String;",false)
            mv.visitMethodInsn(Opcodes.INVOKESTATIC,"android/util/Log","e","(Ljava/lang/String;Ljava/lang/String;)I",false)
            mv.visitInsn(Opcodes.POP)

        }
        super.visitInsn(opcode)
    }
}

2.onClick事件添加防快速点击

注意:onClick方法名也不一定是View.OnClickListener的onClick 所以在指定onClick时需要指定下方法的所属 具体实现是在过滤方法时添加判断 指定下onClick的方法的参数以及返回类型信息

//示例代码
public fun click(){
    val bt = findViewById<TextView>(R.id.mainBt);
    bt.setOnClickListener{
        //此处if分支为即将插入的代码
        if (FastClickUtils.isFastClick()){
            return@setOnClickListener
        }
        //doSomething
    }
 }
//需要插入代码的字节码
LINENUMBER 41 L0
GETSTATIC com/example/utils/FastClickUtils.Companion : Lcom/example/utils/FastClickUtils$Companion;
INVOKEVIRTUAL com/example/utils/FastClickUtils$Companion.isFastClick ()Z
IFEQ L1
L2
LINENUMBER 42 L2
RETURN
L1
//MethodVisitor中的具体实现
class TransformOnclickMethodVisitor extends MethodVisitor{

    TransformOnclickMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv)
    }

    private Label L1 = new Label()

    @Override
    void visitCode() {
        super.visitCode()
        //方法前插入
        mv.visitFieldInsn(Opcodes.GETSTATIC,"com/example/utils/FastClickUtils","Companion","Lcom/example/utils/FastClickUtils\$Companion;")
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"com/example/utils/FastClickUtils\$Companion","isFastClick","()Z",false)
        mv.visitJumpInsn(Opcodes.IFEQ,L1)
        mv.visitInsn(Opcodes.RETURN)
        mv.visitLabel(L1)
    }

    @Override
    void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN){
        }
        super.visitInsn(opcode)
    }

}

3.给方法加try catch (适用场景:某些三方sdk造成的崩溃没有捕获)
//捕获异常
fun tryCatch(){
    //异常代码
    try {
        val number = 10/0
    }catch (e: Exception){
        e.printStackTrace()
    }
}
//上述代码转字节码
 TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
   L3
    LINENUMBER 52 L3
   L0
    NOP
   L4  //L4 此处为运算部分 不需要再次插入 val number = 10/0
    LINENUMBER 53 L4
    BIPUSH 10
    ICONST_0
    IDIV
    ISTORE 1
   L1
    GOTO L5
   L2
    LINENUMBER 54 L2
    ASTORE 1
   L6
    LINENUMBER 55 L6
    ALOAD 1
    INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
   L7
    LINENUMBER 56 L7
   L5
class TransformTryCatchMethodVisitor extends MethodVisitor{

    private def l0 = new Label(),l1 = new Label(),l2 = new Label()

    TransformTryCatchMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv)
    }

    @Override
    void visitCode() {
        super.visitCode()
        //全局加tryCatch  try{ 部分
        mv.visitTryCatchBlock(l0,l1,l2,"java/lang/Exception")
        mv.visitLabel(l0)
        mv.visitInsn(Opcodes.NOP)

    }


    @Override
    void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN){
            // } catch(e : Exception) { e.printStackTrace() } 部分
            mv.visitLabel(l1)
            def label = new Label()
            mv.visitJumpInsn(Opcodes.GOTO,label)
            visitLabel(l2)
            mv.visitVarInsn(Opcodes.ASTORE,1)
            mv.visitVarInsn(Opcodes.ALOAD,1)
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"java/lang/Exception","printStackTrace","()V",false)
            mv.visitLabel(label)
        }
        super.visitInsn(opcode)
    }
}

4.替换new Thread 为自定义的Thread

替换的核心思想
1.找到 NEW 指令 将NEW 指令后的Thread对象替换为自己的Thread
2.替换构造函数的对象 每个对象都至少都会有一个默认的构造函数

以下面字节码为例
NEW java/lang/Thread
INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)V
需要将java/lang/Thread 替换为自己的Thread全路径名 eq: com/xxx/xxx/MyThread

//替换Thread
private fun replaceThread(){
    Thread{
    }.start()
}
//创建Thread对象的字节码内容
L2
    LINENUMBER 51 L2
    NEW java/lang/Thread //创建对象指令
    DUP
    GETSTATIC com/example/aaaa/MainActivity$replaceThread$1.INSTANCE : Lcom/example/aaaa/MainActivity$replaceThread$1;
    CHECKCAST java/lang/Runnable
    INVOKESPECIAL java/lang/Thread.<init> (Ljava/lang/Runnable;)V //初始化构造函数
   L3
    LINENUMBER 52 L3
    INVOKEVIRTUAL java/lang/Thread.start ()V
//具体替换过程
class TransformThreadMethodVisitor extends MethodVisitor{

    private final static String THREAD = "java/lang/Thread"  //系统Thread的路径
    private final static String MY_THREAD = "com/example/aaaa/MyThread" //自定义Thread的路径

    TransformThreadMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv)
    }

    @Override
    void visitTypeInsn(int opcode, String type) {
        //替换NEW指令的Thread
        if (opcode == Opcodes.NEW && type == THREAD){
            mv.visitTypeInsn(Opcodes.NEW,MY_THREAD)
            return
        }
        super.visitTypeInsn(opcode, type)
    }

    @Override
    void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        //替换init的Thread
        if (owner == THREAD && name.equalsIgnoreCase("<init>")){
            mv.visitMethodInsn(opcode,MY_THREAD,name,descriptor,isInterface)
            return
        }
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
    }

    @Override
    void visitCode() {
        super.visitCode()
    }


    @Override
    void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN){
        }
        super.visitInsn(opcode)
    }
}

Demo

Demo地址

推荐阅读更多精彩内容