一步一步实现简单安卓性能监控SDK之熟悉ASM

我们已经了解了java字节码基本的知识,我们应该能对java运行的一些原理,以及优化有了一个基本的认识。
但是,如果这个时候,直接手写字节码,还是很痛苦的。好在有些类库已经为我们写好了,我们直接拿来用,能省好多事情。
正所谓,站在巨人的肩膀!!!

选择哪一个字节码操作库?

大家熟知的字节码例如javasist ASM 等等。其中,ASM用的算是比较广泛。因为它小巧,效率高!

ASM 内部的逻辑是使用的是访问者模式的。具体可以参考我的另一篇文章:http://www.jianshu.com/p/765dfe6c7e02

访问者模式的好处是:分离算法的实现和算法操作结构之间的耦合,这样 就能在不改变被操作的数据结构的情况下,增加对数据的操作方法。

ASM

在ASM中,accpter包括:ClassReader class and the MethodNode class
他们的方法原型是
<pre>
void accept(ClassVisitor cv)
void accept(MethodVisitor mv)
</pre>
Visitor 接口包括 ClassVisitor, AnnotationVisitor, FieldVisitor, 和 MethodVisitor.这些visit方法的签名大致如下
<pre>
void visit(int version, int access, String name, String signature, String superName, String[] interfaces)

AnnotationVisitor visitAnnotation(String desc, boolean visible)

void visitAttribute(Attribute attr)

void visitEnd()

FieldVisitor visitField(int access, String name, String desc, String signature, Object value)

void visitInnerClass(String name, String outerName, String innerName, int access)

MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)

void visitOuterClass(String owner, String name, String desc)

void visitSource(String source, String debug)
</pre>

时序图

Paste_Image.png

基本上时序图就是在不停的访问类中的区块

初识ASM

我们通过一段代码来简单的认识下ASM
下面的代码的作用是,复制一份class文件
<pre>
public static void main(String args []){
String classFilePath ="F:\Main.class";
String classFilePathDest ="F:\MainCopy.class";

    FileInputStream fis = null;
    try {
        fis = new FileInputStream(classFilePath);
        ClassReader cr  = new ClassReader(fis);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cr.accept(cw,0);

        FileOutputStream fos = new FileOutputStream(classFilePathDest);
        fos.write(cw.toByteArray());;
        fos.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

</pre>

分析,通过fileInputstream读取一个class文件流之后,使ClassReader去读取这个字节流。
ClassWriter是继承自ClassVisitor的 ,ClassWriter.COMPUTE_FRAMES 代表让asm计算堆栈帧。
cr.accept(cw,0);驱使cw去不断的产生一系列的visit事件,达到复制一份相同的class文件的目的

小例子,在每一个方法的执行之前和执行之后,都插入一段代码

这里定义一个改写method的适配器,继承自ClassVisitor,只需要覆盖掉visitMethod方法即可。我们在这个方法中拦截,这个方法有一个返回值叫methodVisitor,然后定义一个methodvisitor类直接去执行改写逻辑即可。
其中
<pre>
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn(" before method exec , method in "+ owner +" ,name="+name);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
</pre>
可以通过字节码反编译工具查看的!

完整的adapter代码如下

<pre>
package com.zxy.test.javaagent.hello.asm.wrapmethod;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**

  • Created by zxy on 2017/3/30.
    */
    public class WrapMethodClassAdapter extends ClassVisitor implements Opcodes {

    public WrapMethodClassAdapter(final ClassVisitor cv) {
    super(ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
    MethodVisitor mv = super.visitMethod(i, s, s1, s2, strings);
    return ( mv == null ) ? null :new WrapMethodVisitor(mv);
    }

    public static class WrapMethodVisitor extends MethodVisitor {

     public WrapMethodVisitor( MethodVisitor mv) {
         super(ASM5,mv);
     }
    
     @Override
     public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
         //方法执行之前打印
         mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
         mv.visitLdcInsn(" before method exec , method in "+ owner +" ,name="+name);
         mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    
         mv.visitMethodInsn(opcode, owner, name, desc, itf);
    
         //方法执行之后打印
         mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
         mv.visitLdcInsn(" after method exec , method in "+ owner +" ,name="+name);
         mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    
     }
    

    }
    }

</pre>

调用代码

感觉下,和之前代码有啥区别啊
<pre>
public static void main(String args []){
String classFilePath ="F:\Main.class";
String classFilePathDest ="F:\MainCopy.class";
FileInputStream fis = null;
try {
fis = new FileInputStream(classFilePath);
ClassReader cr = new ClassReader(fis);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
WrapMethodClassAdapter wrapMethodClassAdapter = new WrapMethodClassAdapter(cw);
//cr.accept(cw,0);
cr.accept(wrapMethodClassAdapter,0);
FileOutputStream fos = new FileOutputStream(classFilePathDest);
fos.write(cw.toByteArray());;
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
</pre>

运行结果

插码之前.png
插码之后

参考:
http://download.forge.objectweb.org/asm/asm4-guide.pdf
http://asm.ow2.org/asm50/javadoc/user/index.html
http://asm.ow2.org/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容