Android开发中,ASM是一个十分重要的概念。它提供了一种在运行时(Runtime)修正已有类或动态生成新类的办法,为开发者提供了更多的灵敏性和控制权。

什么是ASM?

ASM全称为“Java字节码操作结构(Java Bytecode Manipulation Framework)”,它是一个用于生成和转化Java字节码的结构。它可以让你在运行时动态地生成、修正和转化Java字节码,可以做到诸如在类加载时动态修正字节码,或许在执行过程中动态生成新的类等等。

ASM是一个十分轻量级的结构,它的体积不到100KB,可是却具有十分强壮的功用。它不仅可以生成类文件,还可以修正已有的类文件,而且这些操作都是在内存中进行的,不需求写入文件。

ASM与反射的差异

在Java中,反射是一个十分常用的功用,它可以让咱们在运行时获取类的信息,而且动态地调用类的办法和属性。尽管ASM和反射都是在运行时进行操作,可是它们之间仍是有很大的差异的。

首先,反射是在已有的类上进行操作,而ASM可以动态生成新的类或许修正已有的类。这就意味着ASM在某些情况下可以比反射愈加灵敏和强壮。

其次,ASM操作的是字节码,而反射操作的是类的元数据。这就意味着ASM可以做到一些反射无法做到的事情,比方修正类的承继联系、修正类的拜访权限等等。

ASM的运用场景

ASM可以用在很多场景中,比方:

  • 代码注入
  • AOP(面向切面编程)
  • 动态署理
  • 字节码加密和混杂
  • 动态生成类和办法

除此之外,ASM还可以用来做功用优化。经过对字节码的修正,咱们可以让程序在执行时愈加高效。比方可以将循环展开、将办法内联、将常量提取等等。

ASM的基本概念

在运用ASM时,有一些基本概念需求了解:

ClassVisitor

ClassVisitor是ASM中的一个中心接口,它用于拜访类的结构。咱们可以经过完成ClassVisitor来修正类的结构。

MethodVisitor

MethodVisitor是ClassVisitor的子接口,它用于拜访办法的结构。咱们可以经过完成MethodVisitor来修正办法的结构。

FieldVisitor

FieldVisitor是ClassVisitor的子接口,它用于拜访字段的结构。咱们可以经过完成FieldVisitor来修正字段的结构。

Type

Type是ASM中的一个中心类,它用于表示Java中的类型。咱们可以经过Type来获取一个类的信息,比方类的称号、类的办法、类的字段等等。

ASMifier

ASMifier是ASM中的一个东西类,它可以将一个已有的类转化成ASM的代码。这个东西类十分有用,可以帮助咱们了解ASM的运用。

ASM的实例

让咱们经过一些实例来了解ASM的运用。

1. 代码注入

运用ASM进行代码注入,可以在运行时动态地向已有的办法中参加一些新的代码。比方在办法的最初参加一些日志输出、在办法的结束参加一些计算代码等等。

下面是一个简略的代码注入的比如:

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassWriter classWriter) {
        super(Opcodes.ASM5, classWriter);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("test".equals(name)) {
            mv = new MethodVisitor(Opcodes.ASM5, mv) {
                @Override
                public void visitCode() {
                    super.visitCode();
                    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                    mv.visitLdcInsn("Entering method test");
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                }
                @Override
                public void visitInsn(int opcode) {
                    if (opcode == Opcodes.RETURN) {
                        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("Exiting method test");
                        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                    }
                    super.visitInsn(opcode);
                }
            };
        }
        return mv;
    }
}

在这个比如中,咱们完成了一个MyClassVisitor类,承继了ClassVisitor。在visitMethod办法中,咱们经过判别办法名是否为“test”,来获取办法的MethodVisitor。在MethodVisitor中,咱们在办法的最初参加了一条输出“Entering method test”的代码,在办法的结束参加了一条输出“Exiting method test”的代码。这样每次调用test办法时,都会输出这两条日志信息。

2. AOP(面向切面编程)

AOP是一种编程思维,它可以将一些横切性的功用(比方日志、权限、业务等)一致地应用到多个办法中。运用ASM进行AOP编程,可以在运行时动态地将这些横切性的功用参加到多个办法中。

下面是一个简略的AOP的比如:

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassWriter classWriter) {
        super(Opcodes.ASM5, classWriter);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("test".equals(name)) {
            mv = new MethodVisitor(Opcodes.ASM5, mv) {
                @Override
                public void visitCode() {
                    mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/LogInterceptor", "before", "()V", false);
                    super.visitCode();
                }
                @Override
                public void visitInsn(int opcode) {
                    super.visitInsn(opcode);
                    if (opcode == Opcodes.RETURN) {
                        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/LogInterceptor", "after", "()V", false);
                    }
                }
            };
        }
        return mv;
    }
}

在这个比如中,咱们完成了一个MyClassVisitor类,承继了ClassVisitor。在visitMethod办法中,咱们经过判别办法名是否为“test”,来获取办法的MethodVisitor。在MethodVisitor中,咱们在办法的最初参加了一条调用“com/example/LogInterceptor”的before办法的代码,在办法的结束参加了一条调用“com/example/LogInterceptor”的after办法的代码。这样每次调用test办法时,都会先调用before办法,然后调用test办法,最终调用after办法。

3. 动态署理

动态署理是一种常用的规划形式,它可以让咱们在运行时动态地生成一个署理目标。运用ASM进行动态署理,可以在运行时动态地生成一个完成了指定接口的署理目标。

下面是一个简略的动态署理的比如:

public class MyClassVisitor extends ClassVisitor {
    private String className;
    private String interfaceName;
    public MyClassVisitor(ClassWriter classWriter, String className, String interfaceName) {
        super(Opcodes.ASM5, classWriter);
        this.className = className;
        this.interfaceName = interfaceName;
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, className, signature, "java/lang/Object", new String[]{interfaceName});
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("invoke".equals(name)) {
            mv = new MethodVisitor(Opcodes.ASM5, mv) {
                @Override
                public void visitCode() {
                    super.visitCode();
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                    mv.visitFieldInsn(Opcodes.GETFIELD, className, "handler", "Ljava/lang/Object;");
                    mv.visitTypeInsn(Opcodes.CHECKCAST, interfaceName);
                    mv.visitVarInsn(Opcodes.ALOAD, 1);
                    mv.visitVarInsn(Opcodes.ALOAD, 2);
                    mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfaceName, "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
                    mv.visitInsn(Opcodes.ARETURN);
                }
            };
        }
        return mv;
    }
}

在这个比如中,咱们完成了一个MyClassVisitor类,承继了ClassVisitor。在visit办法中,咱们将类的称号修正为指定的称号,将类完成的接口修正为指定的接口。在visitMethod办法中,咱们经过判别办法名是否为“invoke”,来获取办法的MethodVisitor

运用ASM进行字节码加密和混杂

运用ASM可以对字节码进行加密和混杂,增强代码的安全性。可以经过修正常量池、修正办法名和字段名等办法来到达加密和混杂的效果。

下面是一个简略的字节码加密和混杂的比如:

public class MyClassVisitor extends ClassVisitor {
    private String key;
    public MyClassVisitor(ClassWriter classWriter, String key) {
        super(Opcodes.ASM5, classWriter);
        this.key = key;
    }
    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0) {
            return super.visitField(access, name, descriptor, signature, value);
        } else {
            FieldVisitor fv = super.visitField(access, name + "_" + key, descriptor, signature, value);
            fv.visitAnnotation("Lcom/example/Encrypted;", true);
            return fv;
        }
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0) {
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        } else {
            MethodVisitor mv = super.visitMethod(access, name + "_" + key, descriptor, signature, exceptions);
            mv.visitAnnotation("Lcom/example/Obfuscated;", true);
            return mv;
        }
    }
    @Override
    public void visitEnd() {
        AnnotationVisitor av = super.visitAnnotation("Lcom/example/EncryptedClass;", true);
        av.visit("key", key);
        super.visitEnd();
    }
}

在这个比如中,咱们完成了一个MyClassVisitor类,承继了ClassVisitor。在visitField办法中,咱们判别字段是否为公有或许受维护的,假如是,则不做处理;假如不是,则将字段名加上指定的key,而且加上注解“@Encrypted”。在visitMethod办法中,咱们同样地判别办法是否为公有或许受维护的,假如是,则不做处理;假如不是,则将办法名加上指定的key,而且加上注解“@Obfuscated”。在visitEnd办法中,咱们加上注解“@EncryptedClass”,而且增加一个属性“key”,代表加密的key。

运用ASM动态生成类和办法

运用ASM可以动态生成类和办法,这是动态署理、AOP等功用的基础。可以运用ASM生成的类,承继已有的类或许完成指定的接口,具有与原类相似的功用。

下面是一个简略的动态生成类和办法的比如:

public class MyClassWriter extends ClassWriter {
    public MyClassWriter() {
        super(ClassWriter.COMPUTE_MAXS);
    }
    public void generateClass() {
        visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/example/DynamicClass", null, "java/lang/Object", null);
        // 增加字段
        visitField(Opcodes.ACC_PRIVATE, "name", "Ljava/lang/String;", null, null).visitEnd();
        // 增加构造办法
        MethodVisitor constructorVisitor = visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructorVisitor.visitCode();
        constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        constructorVisitor.visitInsn(Opcodes.RETURN);
        constructorVisitor.visitMaxs(0, 0);
        constructorVisitor.visitEnd();
        // 增加setName办法
        MethodVisitor setNameVisitor = visitMethod(Opcodes.ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
        setNameVisitor.visitCode();
        setNameVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        setNameVisitor.visitVarInsn(Opcodes.ALOAD, 1);
        setNameVisitor.visitFieldInsn(Opcodes.PUTFIELD, "com/example/DynamicClass", "name", "Ljava/lang/String;");
        setNameVisitor.visitInsn(Opcodes.RETURN);
        setNameVisitor.visitMaxs(0, 0);
        setNameVisitor.visitEnd();
        // 增加getName办法
        MethodVisitor getNameVisitor = visitMethod(Opcodes.ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
        getNameVisitor.visitCode();
        getNameVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        getNameVisitor.visitFieldInsn(Opcodes.GETFIELD, "com/example/DynamicClass", "name", "Ljava/lang/String;");
        getNameVisitor.visitInsn(Opcodes.ARETURN);
        getNameVisitor.visitMaxs(0, 0);
        getNameVisitor.visitEnd();
        visitEnd();
    }
}

在这个比如中,咱们完成了一个MyClassWriter类,承继了ClassWriter。在generateClass办法中,咱们运用visit办法生成了一个名为“com/example/DynamicClass”的类,承继自Object类。接着,咱们增加了一个私有字段“name”,一个公有的构造办法和两个公有的办法“setName”和“getName”。在这两个办法中,咱们运用visitFieldInsn等办法,拜访了字段“name”的值,而且返回了它。这样,咱们就经过ASM动态生成了一个类和其间的办法。

总结

本文介绍了ASM的基本概念、运用场景以及一个简略的实例。尽管ASM的运用需求必定的学习成本,可是它可以为咱们提供愈加灵敏和强壮的功用,帮助咱们解决一些难题。假如你还没有运用过ASM,我建议你去尝试一下,相信你会有新的发现

引荐

android_startup: 提供一种在应用发动时可以愈加简略、高效的办法来初始化组件,优化发动速度。不仅支撑Jetpack App Startup的悉数功用,还提供额外的同步与异步等候、线程控制与多进程支撑等功用。

AwesomeGithub: 根据Github的客户端,纯练习项目,支撑组件化开发,支撑账户密码与认证登陆。运用Kotlin语言进行开发,项目架构是根据JetPack&DataBinding的MVVM;项目中运用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等盛行开源技术。

flutter_github: 根据Flutter的跨渠道版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合具体的Demo来全面解析Android相关的知识点, 帮助读者可以更快的把握与了解所阐述的关键。

daily_algorithm: 每日一算法,由浅入深,欢迎参加一同共勉。