Android——ASM 极速上手 简单使用
Posted 化作孤岛的瓜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——ASM 极速上手 简单使用相关的知识,希望对你有一定的参考价值。
理解一个工具的最快方式就是跑起来,然后原理自然了然于心
本文以一个最简单的demo来实现对ASM全过程的了解。创建一个Child类,有一个call方法,最终的目的是在class类的call方法下增加一行输出语句。
ASM概念,操作流程:
需要创建一个 ClassReader 对象,将 .class 文件的内容读入到一个字节数组中
然后需要一个 ClassWriter 的对象将操作之后的字节码的字节数组回写
需要事件过滤器 ClassVisitor。在调用 ClassVisitor 的某些方法时会产生一个新的 XXXVisitor 对象,当我们需要修改对应的内容时只要实现自己的 XXXVisitor 并返回就可以了
1.引入最新的依赖:
//ASM相关依赖
implementation 'org.ow2.asm:asm:9.2'
implementation 'org.ow2.asm:asm-commons:9.1'
2.创建Child类,有一个phone属性,以及call()方法
public class Child {
public String phone;
public void call() {
System.out.println("call");
}
}
最终目的是在方法下面加一句,输入手机的名字和价格:
System.out.println("use :" + this.phone + " with price :" + this.price + " call");
3.创建方法过滤器ChildMethod,这里没有用MethodVisitor,使用了AdviceAdapter,因为它是 MethodVisitor 的子类,功能更全。
public class ChildMethod extends AdviceAdapter implements Opcodes {
public ChildMethod(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
@Override
public void visitCode() {
//表示 ASM 开始扫描这个方法
super.visitCode();
}
@Override
public void visitEnd() {
//表示方法扫码完毕
super.visitEnd();
}
@Override
protected void onMethodEnter() {
//进入这个方法
super.onMethodEnter();
}
@Override
protected void onMethodExit(int opcode) {
//即将从这个方法出去
super.onMethodExit(opcode);
Label label1 = new Label();
mv.visitLabel(label1);
mv.visitLineNumber(16, label1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("use :");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "com/ng/ngstatistical/test/asmhook/Child", "phone", "Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(" with price :");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "com/ng/ngstatistical/test/asmhook/Child", "price", "Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(" call");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
//扫描Opcodes操作符
super.visitInsn(opcode);
}
}
可以看到,添加小小的一行源代码却需要我们编写这么多的字节码,其实这里有一个取巧的办法,就是先在Child.java类中输入代码,实现我们想要的效果,再使用ASM Bytecode Viewer 插件来看生成的字节码,如下图所示:
之后,再复制到我们的方法过滤器的指定位置就好了~如此简单~高效~快捷~
4.然后需要实现类过滤器,在类过滤器中按方法名过滤方法,然后调用我们刚刚实现的方法过滤器:
public class ChildClassVisitor extends ClassVisitor {
/**
* @param api asm的api版本
*/
public ChildClassVisitor(int api) {
super(api);
}
public ChildClassVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if (name.equals("call")) {
return new ChildMethod(Opcodes.ASM9,methodVisitor,access,name,descriptor);
}
return methodVisitor;
}
}
5.最后需要新创建一个类,编写main函数,首先需要调用一下Child类的call方法来生成class文件:
//生成class
private static void testChild() {
Child child = new Child();
child.call();
}
public static void main(String[] args) {
testChild();
//startHook();
}
运行之后,可以看到生成的class:
6.最后在main函数执行asm的调度方法:
//Child 的 class文件路径
public static final String LOCAL_PATH = "/Users/xiaoguagua/androidProjects/MyProjects/ng_projects/NgStatistical/app/build/intermediates/javac/debug/classes/com/ng/ngstatistical/test/asmhook";
private static void startHook() {
try {
//1.首先创建ClassReader,读取目标类Child的内容
ClassReader cr = new ClassReader(Child.class.getName());
//2.然后创建ClassWriter对象,
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new ChildClassVisitor(ASM9, cw);
cr.accept(cv, Opcodes.ASM9);
// 获取生成的class文件对应的二进制流
byte[] code = cw.toByteArray();
//将二进制流写到out/下
FileOutputStream fos = new FileOutputStream(LOCAL_PATH + "/Child.class");
fos.write(code);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
运行,再去看刚才的Child.class文件,发现,它已经被改掉了,成功~
以上是关于Android——ASM 极速上手 简单使用的主要内容,如果未能解决你的问题,请参考以下文章