ASM 学习心得

背景

阅读了ASM 字节码插桩:实现双击防抖这篇博客之后,激发了实际操作的念头。在动手编程过程中,遇到了一些新问题,记录一下解决方案。

Aop编程第一步:确定 Hook 点

ASM字节码插桩的对象不是Java代码或Kotlin代码,需要从字节码中找规律,找到可以定位的Hook点。

以“实现双击防抖”为例子,说明一下操作步骤:

  1. 枚举出所有给View设置onClickListener的可能写法,包括内部类,Lambda表达式等不同写法
  2. 编译代码后,分析生成的class类文件
  3. 通过javap -v -p xx.class命令,查看字节码的规律,若遇到难以理解的命令,随时向ChatGPT寻求指导。

Aop编程第二步:编写 ASM 代码

一般是通过“ASM Bytecode Viewer”插件来看查看字节码所对就的ASM代码,实际操作过程中,我遇到三个问题:

  1. 在升级Android Studio到2022.3.1版本后,运行“ASM Bytecode Viewer”插件总是报错
  2. Android Studio不显示编译后的内部类文件,只能看到外部类文件
  3. 通过“ASM Bytecode Viewer”生成的ASM代码包括了Debug调试信息和字节码验证信息,需要手动删减代码

通过ChatGPT4的辅助,编写了一个功能类似“ASM Bytecode Viewer”的Java工具类,完美的解决了上述问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
object BytecodeToASM {

fun toASM(classPath: String){
val inputStream = FileInputStream(classPath)
val classReader = ClassReader(inputStream)

val printWriter = PrintWriter(System.out)
val traceClassVisitor = TraceClassVisitor(null, ASMifier(), printWriter)

// ClassReader.SKIP_DEBUG and ClassReader.SKIP_FRAMES 去掉 Debug调试信息 和 字节码验证信息
classReader.accept(traceClassVisitor, ClassReader.SKIP_DEBUG and ClassReader.SKIP_FRAMES)
}

fun toBytecodeByJavap(classPath: String) {
val process = Runtime.getRuntime().exec("javap -v -p $classPath")
val reader = BufferedReader(InputStreamReader(process.inputStream))

reader.useLines { lines ->
lines.forEach { println(it) }
}
}

}

fun main(args: Array<String>) {
val classPath = ""

println("----------以下是字节码 ----------")
BytecodeToASM.toBytecodeByJavap(classPath)

println("----------以下是ASM代码 ----------")
BytecodeToASM.toASM(classPath)
}

以实现双击防抖为例:

Java代码

1
2
3
if(!ViewClickMonitor.onClick(view)) {
return
}

字节码:

1
2
3
4
6: aload_1
7: invokestatic #34 // Method github/leavesczy/track/click/view/ViewClickMonitor.onClick:(Landroid/view/View;)Z
10: ifne 14
13: return

ASM代码:

1
2
3
4
5
6
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESTATIC, "github/leavesczy/track/click/view/ViewClickMonitor", "onClick", "(Landroid/view/View;)Z", false);
Label label2 = new Label();
methodVisitor.visitJumpInsn(IFNE, label2);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLabel(label2);
感谢您的阅读,本文由 刘阳 版权所有。如若转载,请注明出处:刘阳(https://handsomeliuyang.github.io/2023/08/08/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/ASM%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97/
App的Repo多仓库管理