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地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容