在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: 每日一算法,由浅入深,欢迎参加一同共勉。