天天看点

使用ASM编写 打印方法运行的时间 代码分析

请先简单阅读下原文代码

https://blog.csdn.net/weixin_44618248/article/details/107086410

可以对照源代码在阅读本文同时可以看得更清晰一些

注⚠️:以下属于个人学习,理解 如果偏颇过深 非常欢迎在评论看到您的看法和想法

先看前一段代码

val startTimeLabel = newLabel()  //标签
    val endTimeLabel = newLabel()  //标签
           

⬆️ 用于向 本地变量表1⃣️ 中插入变量时候使用

var startTimeIndex: Int = 0
           

⬆️ 用于记录 变量在 本地变量表1⃣️ 里面的位置

因为待会要从这个 本地变量表1⃣️ 里面去取数据

override fun onMethodEnter() {
        super.onMethodEnter()

        startTimeIndex = newLocal(Type.DOUBLE_TYPE)
           

⬆️ 通过 newLocal方法新建一个类型为 Double 的变量并记录它在 本地变量表1⃣️ 中的位置

startTimeLabel.let {
            visitLabel(it)
        }
           

⬆️ 记录label的顺序 表示在这一行开始

mv.visitLocalVariable(
            "startTime",
            "J",
            null,
            startTimeLabel,
            endTimeLabel,
            startTimeIndex
        )
           

⬆️ 向 本地变量表1⃣️ 中声明变量 :

  • 变量名称叫做startTime
  • 类型为 J2⃣️
  • 没有泛形
  • 变量存活生命周期开始于 startTimeLabel
  • 变量存活生命周期结束于 endTimeLabel
  • 它的下标位置在 startTimeIndex
mv.visitMethodInsn(
            Opcodes.INVOKESTATIC,
            "android/os/SystemClock",
            "currentThreadTimeMillis",
            "()J",
            false
        )
           

⬆️ 在该字节码下一行插入语句:

SystemClock.currentThreadTimeMillis()

对应位置参数:

  • Opcodes.INVOKESTATIC -> 表示执行一个类的静态方法
  • 类叫做android/os/SystemClock
  • 方法名称叫 currentThreadTimeMillis
  • 方法的描述符号3⃣️ 为()J
println("插入本地变量表index=${startTimeIndex}")

        mv.visitVarInsn(Opcodes.LSTORE, startTimeIndex)

    }
           

⬆️ 把刚刚方法的返回值 存到下标为 startTimeIndex 的本地变量表中

Opcodes.LSTORE 是指令 表示存一个Long类型的变量

为什么这样就存入到 本地变量表?因为方法执行后 返回值还在栈中 可以继续用指令

操作栈中数据

方法即将要执行完毕的时候给他插入一些字节码

override fun onMethodExit(opcode: Int) {

        //求时间差 并存入 本地变量表
        mv.visitMethodInsn(
            Opcodes.INVOKESTATIC,
            "android/os/SystemClock",
            "currentThreadTimeMillis",
            "()J",
            false
        )
           

⬆️ 执行方法 SystemClock.currentThreadTimeMillis()

mv.visitVarInsn(Opcodes.LLOAD, startTimeIndex)
           

⬆️ 从下标为 startTimeIndex 的 本地变量表1⃣️ 中获取值 并放入栈

mv.visitInsn(Opcodes.LSUB)
           

⬆️ 栈中元素 进行相减 操作

mv.visitVarInsn(Opcodes.LSTORE, startTimeIndex)
           

⬆️ 栈中元素 存入到下标为 startTimeIndex 的 本地变量表1⃣️ 中

//StringBuilder 构建str 并存入本地变量表
        val strVarIndex = newLocal(Type.getType(String::class.java))
        val strBeginLabel = newLabel()
        val strEndLabel = newLabel()
        mv.visitLocalVariable(
            "__tempStr",
            "Ljava/lang/String;",
            null,
            strBeginLabel,
            strEndLabel,
            strVarIndex
        )
        strBeginLabel.let {
            visitLabel(it)
        }
           

⬆️ 创建变量 __tempStr (前面有过类似 就不在赘述)

//NEW java/lang/StringBuilder
        mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")
           

⬆️ new 一个对象 对象为StringBuilder

//    DUP
        mv.visitInsn(Opcodes.DUP)
           

⬆️ 复制栈顶数值并将复制值压入栈顶

//    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        mv.visitMethodInsn(
            Opcodes.INVOKESPECIAL,
            "java/lang/StringBuilder",
            "<init>",
            "()V",
            false
        )
           

⬆️ 调用方法 < init >

//    LDC "Wtf"
        mv.visitLdcInsn(customTAG)
           

⬆️ 向栈中放入字符串(Wtf) customTAG里面的值(Wtf)

//    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/StringBuilder",
            "append",
            "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
            false
        )
           

⬆️ 调用方法 append 相当于 StringBuilder.append(“Wtf”)

//    ILOAD 2
        mv.visitLdcInsn(",方法${name}消耗时间:")
        //    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/StringBuilder",
            "append",
            "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
            false
        )
           

⬆️ 继续调用方法 相当于 StringBuilder.append(",方法${name}消耗时间:")

//求得的时间 入栈
        mv.visitVarInsn(Opcodes.LLOAD, startTimeIndex)
        //append 到 StringBuilder上
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/StringBuilder",
            "append",
            "(J)Ljava/lang/StringBuilder;",
            false
        )
           

⬆️ 从本地变量表中读取 并执行相当于:

StringBuilder.append(startTime)

//    ILOAD 2
        mv.visitLdcInsn("毫秒")
        //    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/StringBuilder",
            "append",
            "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
            false
        )
           

⬆️ 继续调用方法 相当于 StringBuilder.append(“毫秒”)

//toString
        //    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/lang/StringBuilder",
            "toString",
            "()Ljava/lang/String;",
            false
        )
           

⬆️ 继续调用方法 相当于 StringBuilder.toString()

mv.visitVarInsn(Opcodes.ASTORE, strVarIndex)
           

⬆️ 栈中值 存入到 下标为 strVarIndex的变量中

mv.visitFieldInsn(
            Opcodes.GETSTATIC,
            "java/lang/System",
            "out",
            "Ljava/io/PrintStream;"
        )
        mv.visitVarInsn(Opcodes.ALOAD, strVarIndex)
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            "java/io/PrintStream",
            "println",
            "(Ljava/lang/String;)V",
            false
        )
           

⬆️ 继续调用方法 相当于 System.out.println()

endTimeLabel.let {
            visitLabel(it)
        }

        strEndLabel.let {
            visitLabel(it)
        }

        super.onMethodExit(opcode)
    }
           

本地变量表

使用ASM编写 打印方法运行的时间 代码分析

此图是我用javap 命令(javap -c -v xxx.class)获取到的(编译的时候要加额外参数 javac -g xxx.java)

本地变量表是方法内变量的集合 例如实例方法中的this 你可以直接用 是因为 方法默认给你

设置了一个 静态方法就没有 所以你也能明白了吧 在一个类中 它的静态方法为什么不可以调用它的实例方法

字节码中的变量类型

java代码中 字节码中
boolean Z
int I
long J
float F
double D
对象类型 L+全类名;
[] [+对应类型

方法描述符号

规则 ()+返回值

eg: ()V 表示 无入参 且无返回值

(I)V ➡️ fun(Int)

(Ljava/lang/String;)Ljava/lang/String; ➡️ fun(String):String

特殊方法

  • < init > : 构造方法