(“xxx/test/java2/InjectTest.class”);
fos.write(newClassBytes);
fos.close();
关于ASM框架本身的设计,我们这里先不讨论。上面的代码会获取上一步生成的class,然后由ASM执行完插桩之后,将结果输出到test/java2
目录下。其中关键点就在于第2步中,如何进行插桩。
把class数据交给ClassReader
,然后进行分析,类似于XML解析,分析结果会以事件驱动的形式告知给accept
的第一个参数ClassAdapterVisitor
。
public class ClassAdapterVisitor extends ClassVisitor {
public ClassAdapterVisitor(ClassVisitor cv) {
super(Opcodes.ASM7, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
System.out.println(“方法:” + name + " 签名:" + desc);
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
return new MethodAdapterVisitor(api,mv, access, name, desc);
}
}
分析结果通过ClassAdapterVisitor
获得,一个类中会存在方法、注解、属性等,因此ClassReader
会将调用ClassAdapterVisitor
中对应的visitMethod
、visitAnnotation
、visitField
这些visitXX
方法。
我们的目的是进行函数插桩,因此重写visitMethod
方法,在这个方法中我们返回一个MethodVisitor
方法分析器对象。一个方法的参数、注解以及方法体需要在MethodVisitor
中进行分析与处理。
package com.enjoy.asminject.example;
import com.enjoy.asminject.ASMTest;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;
/**
- AdviceAdapter: 子类
- 对methodVisitor进行了扩展, 能让我们更加轻松的进行方法分析
*/
public class MethodAdapterVisitor extends AdviceAdapter {
private boolean inject;
protected MethodAdapterVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
/**
- 分析方法上面的注解
- 在这里干嘛???
-
- 判断当前这个方法是不是使用了injecttime,如果使用了,我们就需要对这个方法插桩
- 没使用,就不管了。
- @param desc
- @param visible
- @return
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (Type.getDescriptor(ASMTest.class).equals(desc)) {
System.out.println(desc);
inject = true;
}
return super.visitAnnotation(desc, visible);
}
private int start;
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办? 记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
start = newLocal(Type.LONG_TYPE); //创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
if (inject){
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
int end = newLocal(Type.LONG_TYPE);
storeLocal(end);
getStatic(Type.getType(“Ljava/lang/System;”),“out”,Type.getType(“Ljava/io” +
“/PrintStream;”));
//分配内存 并dup压入栈顶让下面的INVOKESPECIAL 知道执行谁的构造方法创建StringBuilder
newInstance(Type.getType(“Ljava/lang/StringBuilder;”));
dup();
invokeConstructor(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“”,“()V”));
visitLdcInsn(“execute:”);
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,“(Ljava/lang/String;)Ljava/lang/StringBuilder;”));
//减法
loadLocal(end);
loadLocal(start);
math(SUB,Type.LONG_TYPE);
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“append”,“(J)Ljava/lang/StringBuilder;”));
invokeVirtual(Type.getType(“Ljava/lang/StringBuilder;”),new Method(“toString”,“()Ljava/lang/String;”));
invokeVirtual(Type.getType(“Ljava/io/PrintStream;”),new Method(“println”,“(Ljava/lang/String;)V”));
}
}
}
MethodAdapterVisitor
继承自AdviceAdapter
,其实就是MethodVisitor
的子类,AdviceAdapter
封装了指令插入方法,更为直观与简单。
上述代码中onMethodEnter
进入一个方法时候回调,因此在这个方法中插入指令就是在整个方法最开始加入一些代码。我们需要在这个方法中插入long s = System.currentTimeMillis();
。在onMethodExit
中即方法最后插入输出代码。
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//执行完了怎么办? 记录到本地变量中
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
start = newLocal(Type.LONG_TYPE); //创建本地 LONG类型变量
//记录 方法执行结果给创建的本地变量
storeLocal(start);
}
}
这里面的代码怎么写?其实就是long s = System.currentTimeMillis();
这句代码的相对的指令。我们可以先写一份代码
void test(){
//插入的代码
long s = System.currentTimeMillis();
/**
- 方法实现代码…
*/
//插入的代码
long e = System.currentTimeMillis();
System.out.println(“execute:”+(e-s)+" ms.");
}
然后使用javac
编译成Class再使用javap -c
查看字节码指令。也可以借助插件来查看,就不需要我们手动执行各种命令。
安装完成之后,可以在需要插桩的类源码中点击右键:
点击ASM Bytecode Viewer之后会弹出
所以第20行代码:long s = System.currentTimeMillis();
会包含两个指令:INVOKESTATIC
与LSTORE
。
再回到onMethodEnter
方法中
@Override
protected void onMethodEnter() {
super.onMethodEnter();
if (inject) {
//invokeStatic指令,调用静态方法
invokeStatic(Type.getType(“Ljava/lang/System;”),
new Method(“currentTimeMillis”, “()J”));
//创建本地 LONG类型变量
start = newLocal(Type.LONG_TYPE);
//store指令 将方法执行结果从操作数栈存储到局部变量
storeLocal(start);
}
}
而onMethodExit
也同样根据指令去编写代码即可。最终执行完插桩之后,我们就可以获得修改后的class数据。
四、Android中的实现
在Android中实现,我们需要考虑的第一个问题是如何获得所有的Class文件来判断是否需要插桩。Transform就是干这件事情的。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://img-blog.csdnimg.cn/img_convert/42364196c374cd6083ea0b37c734c50e.jpeg)
尾声
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
- 思维脑图
- 性能优化学习笔记
- 性能优化视频
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
mg-cOLgGbd9-1712452764555)]
- 性能优化视频
[外链图片转存中…(img-3Y2mIKl4-1712452764556)]
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!