前语
所谓的ASM,其实便是怎么生成一个Class文件或许修正一个Class文件的工具,包含对Class里的成员变量或许办法进行添加或修正。比较于Javassist,ASM最大的优点便是功能方面优于Javassist,但随之带来的便是需求开发者精通 class 文件格局和 JVM 指令集。
关注公众号:Android苦做舟
解锁 《Android十三大板块文档》,让学习更靠近未来实战。已构成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载使用大合集,从零开端一同学
4.功能优化大合集,离别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高档工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对作业需求
10.Android根底篇大合集,根基安定楼房平地起
11.Flutter番外篇:Flutter面试+Flutter项目实战+Flutter电子书
12.高档Android组件化强化实战
13.十二模块之弥补部分:其他Android十一大常识系统
敲代码不易,关注一下吧。开端进入正题,ღ( ・ᴗ・` )
一丶用ASM生成Class文件
引入ASM工具库
//核心库
implementation 'org.ow2.asm:asm:9.3'
//辅助库
implementation 'org.ow2.asm:asm-commons:9.3'
首先,先简略生成一个Class文件。运行以下代码,就能够直接生成一个Class文件。
1.1.生成Class 字节码
public static byte[] genClass() {
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "asm/User",null, "java/lang/Object", null); {
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(3, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Lasm/User;", null, label0,label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
1.2.将生成的Class字节码输出
public static void main(String[] args) throws Exception {
//1.生成Class字节码
byte[] genClassByte = genClass();
try {
//输出Class字节码文件
FileOutputStream fos = new FileOutputStream("src/asm/User.class");
fos.write(genClassByte);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
效果如下:
...省掉
package asm;
public class user {
public user() {
}
}
能够看到,比较与Javassist,杂乱的不是一点两点。咱们能够把上面的代码区分为两部分。
分别是ASM工具的运用和Java字节码。如下:
ASM工具的运用,例如ClassWriter,MethodVisitor等
Java字节码,例如: “” 代表的是初始化结构办法,”()V” 代表的是这个办法无参并且无回来参数。
接下来咱们介绍ClassWriter,MethodVisitor类的用法,以及一些JVM字节码常识,高能预警!!
1.3.ClassWriter
很明显,这是一个对Class进行写入操作的类,例如对Class文件添加变量,办法。咱们先看看他的结构办法。 结构办法
ClassWriter classWriter = new ClassWriter(0);
public ClassWriter(final int flags)
关于flags的传值如下:
- flag == 0, 你有必要自己核算栈帧和部分变量以及操作数仓库的巨细 ,也便是你要自己调用visitmax和visitFrame办法。
- flag == ClassWriter. COMPUTE_MAXS,部分变量和操作数仓库部分的巨细会为你核算,还需求调用visitFrame办法设置栈帧。
- flag == ClassWriter.COMPUTE_FRAMES,所有的内容都是主动核算的。 你不用调用visitFrame和visitmax。换句话说,COMPUTE_FRAMES包含
留意:运用ClassWriter. COMPUTE_MAXS他会使得ClassWriter的速度慢10%,ClassWriter.COMPUTE_FRAMES会慢20%。
visit办法
效果:用来界说类的属性
办法运用如下:
例如:生成一个User类。
classWriter.visit(
V1_8,
ACC_PUBLIC | ACC_SUPER,
"asm/User",
null,
"java/lang/Object",
null);
源代码如下:
public final void visit(
final int version,
final int access,
final String name,
final String signature,
final String superName,
final String[] interfaces)
参数解说: version:Java版本号,例如 V1_8 代表Java 8 access:Class拜访权限,一般默许都是 ACC_PUBLIC | ACC_SUPER,部分字段解说如下
- name: Class文件名,例如:asm/User,包名加类名
- signature:类的签名,除非你是泛型类或许完结泛型接口,一般默许null。
- superName:承继的类,很明显所有类默许承继Object。例如:java/lang/Object ,假如是承继 自己写的类Animal,那便是 asm/Animal
- interfaces:完结的接口,例如完结自己写的接口IPrint,那便是
new String[]{"asm/IPrint"}
visitMethod办法
效果:用来界说类的办法
办法运用如下:
例如:生成User的结构办法 public User(){ }
classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
源代码如下:
public final MethodVisitor visitMethod(
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions)
参数解说: access:办法的拜访权限,也便是public,private等 name: 办法名 在Class中,有两个特殊办法名。和,其他的办法都是正常对应Java办法名。
代表的是类的实例结构办法初始化,也便是new 一个类时,肯定会调用的办法。
代表的类的初始化办法,不同于,它不是显现调用的。因为Java虚拟机会主动调用,并且确保在子类的前调用父类的。也便是说,Java虚拟机中,第一个被履行办法的肯定是java.lang.Object。
留意:和履行的机遇不一样,的机遇早于,是在类被加载阶段的初始化进程中调用办法,而办法的调用是在new一个类的实例的时候。
descriptor:办法的描绘符 所谓办法的描绘符,便是字节码对代码中办法的形参和回来值的一个描绘。其实便是一个一一对应的模板。如下:
(IF)V =(表明办法的形参类型描绘符)办法的回来值
关于形参的类型描绘符如下:
办法描绘符如下:
signature:办法签名,除非办法的参数、回来类型和反常运用了泛型,不然一般为 null。
exceptions:办法上的反常。这儿咱们没有抛出任何反常,所以为null。假如throws Exception,exceptions的值为:new String[]{"java/lang/Exception"}
visitField办法
效果:用来界说一个变量
办法运用如下:
例如:生成一个 private int a = 10;
classWriter.visitField(ACC_PRIVATE, "a", "I", null, null);
源代码如下:
public final FieldVisitor visitField(
final int access,
final String name,
final String descriptor,
final String signature,
final Object value)
参数解说:
- access:变量的拜访权限,,也便是public,private等
- name:变量名
- descriptor:变量的描绘符,能够参阅上面的Java类型对应的描绘符
- signature:变量的签名,假如没有运用泛型则为null
- value:变量的初始值。这个字段仅效果于被final修饰的字段,或许接口中声明的变量。其他默许为null,变量的赋值是经过MethodVisitor 的 visitFieldInsn办法。
visitEnd办法
效果:用来告诉Class已经运用完。
toByteArray办法
效果:回来一个字节数组
1.4.MethodVisitor
这是一个用来生成办法的类。
visitCode办法
效果:表明开端生成字节码
通常第一个调用,固定格局
visitLabel办法
效果:设置Label,Label的效果相当于表明办法在字节码中的方位每一个办法都需求一个Label,用来确保办法调用次序。
visitLineNumber办法
效果:界说源代码中的行号与对应的指令
源代码如下:
public void visitLineNumber(final int line, final Label start)
- line:源代码中对应的行号
- start:行号对应的字节码指令
visitVarInsn办法
效果:用来对变量进行加载和存储的指令操作
源代码如下:
public void visitVarInsn(final int opcode, final int varIndex)
参数解说:
opcode:对应的变量字节码指令
例如:获取一个int数值类型的指令对应 iload 0
有获取就肯定会有存储
- varIndex:变量对应在部分变量表的下标 例如下列代码:
int a = 1;
int b = 2;
int d = a + b;
上面代码对应的字节码指令便是
L5
LINENUMBER 13 L5
ICONST_1
ISTORE 1
L6
LINENUMBER 14 L6
ICONST_2
ISTORE 2
L7
LINENUMBER 15 L7
ILOAD 1
ILOAD 2
IADD
ISTORE 3
解说下对应的字节码意义:
- 将1变量加载到操作数栈,对应的指令便是ICONST_1
- 将栈顶的值保存到部分变量表第一个方位,对应的指令便是ISTORE_1
- 将2变量加载到操作数栈,对应的指令便是ICONST_2
- 将栈顶的值保存到部分变量表第二个方位,对应的指令便是ISTORE_2
- 取出部分变量表第一个元素到操作数栈(也便是变量a),对应的指令便是 ILOAD_1
- 取出部分变量表第二个元素到操作数栈(也便是变量b),对应的指令便是 ILOAD_2
- 此刻操作数栈的栈顶就有a和b两个元素,履行指令IADD,就会把栈顶的两个元素相加并将结果压入栈顶
- 将栈顶的值保存到部分变量表第三个方位
visitMethodInsn办法
效果:用来对一个办法履行指令操作
他能够履行的指令如下:
visitInsn办法
效果:用来履行对操作数栈的指令
能够履行的指令如下:
NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.
部分指令说明如下: 详细如下:
visitLocalVariable办法
效果:对部分变量设置
源代码如下:
public void visitLocalVariable(
final String name,
final String descriptor,
final String signature,
final Label start,
final Label end,
final int index)
参数解说:
- name:部分变量名
- descriptor:部分变量名的类型描绘符
- signature:部分变量名的签名,假如没有运用到泛型,则为null
- start:第一个指令对应于这个部分变量的效果域(包含)。
- end:最终一条指令对应于这个部分变量的效果域(排他)。
- index:部分变量名的下标,也便是部分变量名的行号次序(从1开端)。
例如代码:
public void test(){
int a = 1;
int b = 2;
int d = a + b;
}
对应的运用办法如下:
methodVisitor.visitLocalVariable("this", "Lasm/User;","Lasm/User<TT;>;", label0, label4, 0);
methodVisitor.visitLocalVariable("a", "I", null, label1, label4, 1);
methodVisitor.visitLocalVariable("b", "I", null, label2, label4, 2);
methodVisitor.visitLocalVariable("d", "I", null, label3, label4, 3);
留意:每个办法都会默许有一个this的引用
visitMaxs办法
效果:设置这个本地办法最大操作数栈和最大本地变量表
源代码如下:
public void visitMaxs(final int maxStack, final int maxLocals)
例如代码:
public void test(){
int a = 1;
int b = 2;
int d = a + b;
}
对应的运用办法如下:
methodVisitor.visitMaxs(2, 4);
其间
- maxStack == 2,分别是,ICONST_1, IAdd操作
留意:maxStack == 2,不是代表只要两个对操作数栈的指令,而是操作数栈容量巨细为2,能够满意上面代码,例如下面代码,操作数栈巨细为2也能够满意。
public void test(){
int a = 1;
int b = a + 2;
int c = b * 2;
int d = b * 2;
int e = b * 2;
int f = b * 2;
}
再来一个比如, 它的操作数栈巨细有必要为3。
分别是,DUP, INVOKESPECIAL,ASTORE操作
public void test(){
Object user = new Object();
}
- maxLocals == 4,分别是部分变量this,a,b,d。
这些值的效果其实是用来决议操作数栈和本地变量表的巨细,内存优化小常识
留意:visitMaxs办法的调用有必要在所有的MethodVisitor指令完毕后调用。
visitEnd办法
效果:告诉MethodVisitor生成办法完毕,表明完毕生成字节码。
通常是作为MethodVisitor最终一个调用,固定格局。visitCode,一个最前一个最终。
二丶 怎么运用生成的代码
以下代码能够直接仿制,简略修正就能够测试自己的生成的Class文件。
public class ASMDemo {
public static void main(String[] args) throws Exception {
//1.生成Class字节码
byte[] genClassByte = genClass();
try {
//输出Class字节码文件
FileOutputStream fos = new FileOutputStream("src/asm/User.class");
fos.write(genClassByte);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
testClass("asm.User",genClassByte);
}
public static byte[] genClass() {
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(
V1_8,
ACC_PUBLIC | ACC_SUPER,
"asm/User",
null,
"java/lang/Object",
null); {
MethodVisitor methodVisitor = classWriter.visitMethod(
ACC_PUBLIC,
"<init>",
"()V",
null,
null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(3, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Lasm/User;", null, label0,label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
/**
* 测试运行Class
*
* @param name Class完整包名路径
* @param b Class字节码
* @throws Exception
*/
public static void testClass(String name, byte[] b) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
Class clazz = myClassLoader.defineClass(name, b);
Object obj = clazz.newInstance();
//仅为了测试
for (java.lang.reflect.Method method : clazz.getMethods()) {
System.out.println("Method Name:" + method.getName());
if (method.getName().equals("main")) {
method.invoke(obj, new String[1]);
break;
}
if (method.getName().equals("hashCode")) {
Integer code = (Integer) method.invoke(obj, null);
System.out.println("hashCode code:"+code);
}
}
}
public static class MyClassLoader extends ClassLoader{
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
}
三丶怎么快速便利生成Class文件
看到这儿,相信你会有一个疑惑?假如不懂这些字节码那要怎么编写,不用忧虑,有一个ASM Bytecode Viewer能够协助咱们解决这个问题。只要完结下面几步,你也能快速生成一个Class文件。
3.1.下载 ASM Bytecode Viewer 插件
这个就不过多描绘了,在Plugins中自己搜索吧
3.2. 准备好你要写的Java文件
例如你想要生成如下代码:
public class User{
public int b;
public User(int age) {
}
public void test(){
Object user = new Object();
}
}
接下来便是运用 ASM Bytecode Viewer,进程如下
就能够看到如下界面,下面是咱们Java代码真正的字节码内容,但是这不是咱们需求的。持续往下看
// class version 52.0(52)
// access flags 0x21
public class asm/User {
// compiled from:User.java
// access flags 0x1
public I b
// access flags 0x1
public <init>(I)v
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/object.<init> ()v
L1
LINENUMBER 6 L1
RETURN
L2
LOCALVARIABLE this Lasm/User; L0 L2 0
LOCALVARIABLE age I L0 L2 1
MAXSTACK = 1
MAXSTACK = 2
看到下面,这才是咱们需求的。留意是ASMified,然后直接仿制dump办法,调用就能生成一个Class的字节码数组,然后后面便是把字节码数组保存成本地,代码上面有。
pack asm.asm;
import org.objectweb.asm.Annotationvisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Fieldvisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.Methodvisitor;
import org.objectweb.asm.opcodes;
import org.objectweb.asm.Type;
import org.objectweb.Typepath;
public class UserDump implements opcodes {
public static byte[] dump() throws Exception {
ClassWriter classwriter = new classwriter(0);
Fieldvisitor fieldvisitor;
Methodvistor methodvisitor;
Annotationvisitor annotationvisition0;
classwriter.visit(V1_8,ACC_PUBLIC 丨 ACC_SUPER, ''asm/User'',null,''java/lang/object'',null);
classwriter.visitsource(''User.java'',null); {
fieldvisitor = classwriter.visitField(ACC_PUBLIC,''B'',''I'',null,null);
fieldvisitor.visitEnd();
}
}
四丶Android Framework源码-AMS
AMS(ActivityManagerService) 在SystemServer的进程中,是SystemServer中的一个目标;
效果:
-
管理activity的生命周期
-
发动activity
-
与PMS进行交互
Activity->AMS:
-
调用
activity.startActivity()
-
经过
ActivityManage.getService("activity")
得到AMS的BpBinder; -
经过BpBinder发送恳求,调用AMS的
startActivity()
AMS->PMS:
-
AMS和PMS都在
SystemServer
进程中,都是SystemServer
中一个目标 -
经过包名和PMS里的缓存
mPackage
查询到App对应的Package -
运用activity的类名经过PMS里的内部类
PackageManagerInternalImpl
查询到activity对应的包装类ResolveInfo; ps:ResolveInfo
这个javabean里有activityInfo、ServiceInfo
等变量,查询啥就给哪个变量赋值,再回来ResolveInfo;
-
得到
ResolveInfo
里的activityInfo;
-
将
activityInfo
回来给App进程的ActivityThread;` -
ActivityThread
中发送事情 -
ActivityThread
中的Handler目标mH收到159事情,处理 -
经过反射创立Activity目标
-
将Activity目标放到activtes发动记载中
ActivityThread
-
每个使用有一个ActivityThread;是使用的入口;
-
在APP进程中
-
是AMS的缓存中心
-
ActivityThread中的List activtes放了activity的发动记载
ActivityThread中重要的目标:
-
ApplicationThread:AMS回调给ActivityThread数据的桥梁
-
mInstrumentation:管理Application和Activity的生命周期(及创立)
-
mH:Handler,处理ApplicationThread里各种回调函数发送的各种消息
点击桌面App图标发生了什么?
-
点击的APP图标是在独自的Luancher进程,是一个系统App进程
-
Luancher进程恳求SystemServer进程中的AMS去创立使用的根Activity(AndroidMnifest.xml中initen-fifter为Luanche的activity)
-
AMS经过包名让PMS查询到相关使用信息,得到使用的Package;
-
AMS创立activity栈,依据Package拿到根activity的配置节点信息,放到栈中,此刻栈中只要一个根activity的配置节点信息,也便是在栈顶;(此处的栈不是使用层的栈,这个栈只是用来放activity节点信息的)
-
AMS恳求zygote进程创立App进程;zygote进程比较特殊, 运用Socket通讯,而不是binder;zygote是所有使用的孵化器,zygote进程挂掉时,手机会主动重启;
-
zygote进程去fork出App进程;
-
APP进程中的主线程调用
ActivityThread.main()
静态函数,main中创立ActivityThread
目标 -
接着在
ActivityThread.attch()
中创立了一个ApplicationThread
目标,作为和AMS通讯时,回来结果的桥梁; -
App进程经过AMS的binder调用
attachApplication(thread)
恳求AMS获取使用对应的Applaction和栈顶中activity节点信息(进程4),此刻给AMS传过去了一个thread,这个thread便是ApplicationThread
-
AMS将从PMS查到的application节点数据序列化后,调用
thread.bindApplaction
(data数据…)传给ActivityThread;
(此刻代码还会持续往下履行,去获取栈顶activity的节点信息) -
ActivityThread调用sendMessage发送消息
BIND_APPLICATION(110)
给Handler,Handler调用handleBindApplication(data)
-
经过反射实例化Instrumentation目标:负责application和activity的生命周期的管理
-
经过Instrumentation目标反射实例化
new Applaction
目标app -
调用
Instrumentation.callApplactionOnCreate(app)
-
履行
Applaction.onCreate()
-
进程10中AMS持续向下履行查找activity,AMS将查到的栈顶根Activity(LaunchActivity )信息封装到一个业务ClientTransaction中,提交业务并履行,在履行中,调用
thread.scheduleTransaction
(业务数据);(thread为ActivityThread中的ApplicationThread
) -
在
ApplicationThread
回调scheduleTransaction
函数中,发送`EXECUTE_TRANSACTION(159)消息 -
Handler处理
EXECUTE_TRANSACTION
消息,从业务数据中取出LaunchActivity
信息,并调用hanldeLaunchActivity
(activity数据) -
经过Instrumentation目标反射实例化
newActivity()
出目标activity -
履行
activity.attach()
,在attach中创立WMS的桥接署理类;(绘制流程会用到) -
经过
Instrumentation
调用callActivityOnCreate(activity)
-
履行
Activty.onCreate();
-
至此发动页根Activity发动完结;
下图中4-5中少了上面7-23的进程:
7-15创立并发动了Application;
16-22创立并发动了Activity;
使用内activity与activity的跳转是跨进程通讯,还是同一个进程内通讯?
是跨进程通讯;
跳转流程参阅上面的:省去了application的创立进程;
进程3 +进程16-23;
关注公众号:Android苦做舟 解锁 《Android十三大板块文档》,让学习更靠近未来实战。已构成PDF版
内容如下:
1.2022最新Android11位大厂面试专题,128道附答案
2.音视频大合集,从初中高到面试应有尽有
3.Android车载使用大合集,从零开端一同学
4.功能优化大合集,离别优化烦恼
5.Framework大合集,从里到外分析的明明白白
6.Flutter大合集,进阶Flutter高档工程师
7.compose大合集,拥抱新技术
8.Jetpack大合集,全家桶一次吃个够
9.架构大合集,轻松应对作业需求
10.Android根底篇大合集,根基安定楼房平地起
11.Flutter番外篇:Flutter面试+Flutter项目实战+Flutter电子书
12.高档Android组件化强化实战
13.十二模块之弥补部分:其他Android十一大常识系统
敲代码不易,关注一下吧。ღ( ・ᴗ・` )