我们定义一个简单的页面MainActivity
,增加一个加了编译期注解的方法
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@TraceTime
public void fun(){
Log.d(“tt_apm”, “annotated function”);
}
}
它的class文件在工程的app/build/intermediates/classes
目录下,用ASM读取分析
public static void main(String[] args) {
try {
File classFile = new File(“./source/MainActivity.class”);
File dir = new File(“.”);
transformClassFile(dir, classFile)
} catch (Exception e){}
}
private static File transformClassFile(File dir, File sourceFile){
String className = sourceFile.getName();
// 得到class文件二进制流
FileInputStream fileInputStream = new FileInputStream(sourceFile);
byte[] sourceClassBytes = IOUtils.toByteArray(fileInputStream);
// 定义classWriter,用于输出修改后的二进制流
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// 自定义ClassVisitor, 负责字节码的消费
MyClassVisitor myClassVisitor = new MyClassVisitor(classWriter);
// ClassReader负责字节码的读取
ClassReader classReader = new ClassReader(sourceClassBytes);
// 开始字节码处理
classReader.accept(myClassVisitor, 0);
// 生成二进制流并保存成新的文件
byte[] destByte = classWriter.toByteArray();
File modified = new File(dir, className)
if (modified.exists()) {
modified.delete()
}
modified.createNewFile();
new FileOutputStream(modified).write(destByte)
return modified;
}
private static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM6, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println(“visit:access: " + access + " ,name: " + name + " , superName: " + superName + " ,singature: " + signature + “, interfaces: " + interfaces.join(”/”));
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("visitMethod:access: " + access + " ,name: " + name + " , desc: " + descriptor + " ,singature: " + signature);
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
MethodVisitor myMv = new MethodVisitor(Opcodes.ASM6, mv) {
@Override
AnnotationVisitor visitAnnotation(String desc, boolean visible) {
System.out.println("visitAnnotation: desc: " + desc);
return super.visitAnnotation(desc, visible)
}
@Override
void visitCode() {
super.visitCode()
}
}
return myMv;
}
}
我们用ClassReader读取了MainActivity.java
的class文件,并用自定义的ClassVisitor
接收事件。查看输出:
visit:access: 33 ,name: com/example/wangkai/MainActivity , superName: android/support/v7/app/AppCompatActivity ,singature: null, interfaces:
visitMethod:access: 1 ,name: , desc: ()V ,singature: null
visitMethod:access: 4 ,name: onCreate , desc: (Landroid/os/Bundle;)V ,singature: null
visitMethod:access: 1 ,name: fun , desc: ()V ,singature: null
visitAnnotation: desc: Lcom/example/wangkai/annotation/TraceTime;
我们通过visit
回调可以读取到class的名字、父类名和接口,这样就可以判断出一个类是否是我们要插桩的白名单页面,是不是Activity子类以及是否实现了点击事件接口View$onClickListener
(实现对点击事件的监控)
通过visitMethod
我们拿到了方法名,这样就可以判断这个方法是不是我们要监控的生命周期方法。
通过在visitMethod
方法里返回自定义的MethodVisitor
对象,我们拿到了方法上的注解,从而可以知道这个方法是否是要插桩的方法。
visitCode
表示方法开始执行,如果能在这里插入代码,那我们的代码就能在原始代码执行前执行。
我们已经找到了切入点,下一步就是插入代码了。插入代码要难一些,因为我们是在字节码层面操作,插入的也只能是字节码,这就需要对字节码有一定了解。包括局部变量表和操作数栈的概念,常见指令(ALOAD, INVOKEVIRTUAL等)的含义[参考5]
。
这里以实现监听点击事件为例。手动埋点时,我们需要插入这样的代码:
private static class MyClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
ClickAgent.click(v); //待插入代码,方法里获取view的ID和当前时间,实现对点击事件的记录
Log.d(TAG, "onClick: ");
}
}
我们要做的是,通过ASM methodVisitor
提供的API,把ClickAgent.click(v)
的字节码,注入到原始onClick
方法里。查看字节码:
L0
LINENUMBER 27 L0
ALOAD 1
INVOKESTATIC com/example/wangkai/ClickAgent.click (Landroid/view/View;)V
L1
LINENUMBER 28 L1
LDC “MainActivity”
LDC "onClick: "
INVOKESTATIC android/util/Log.d (Ljava/lang/String;Ljava/lang/String;)I
可以看到ClickAgent.click(v)
对应的字节码是两行
ALOAD 1
表示把局部变量表里索引为1的值,推到操作数栈上,也就是参数值View v
。对应到ASM,是methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
INVOKESTATIC com/example/wangkai/ClickAgent.click (Landroid/view/View;)V就是调用ClickAgent的静态方法click。对应到ASM,是methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, “com/example/wangkai/ClickAgent”, “click”, “(Landroid/view/View;)V”, false)
当我们在visitorMethod
回调里判断name、desc和signature和原始方法一致,并且该类实现的interfaces
包含了View$onClickListener
时,就可以注入了。
怎么注入进去呢?这样写就可以了:
@Override
void visitCode() {
super.visitCode()
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, “com/example/wangkai/ClickAgent”, “click”, “(Landroid/view/View;)V”, false)
}
修改后执行,会生成插桩后的class文件,可以用JD_GUI查看插桩后的效果,
实际编码中,我们可以借助于Bydecode Outline
插件。
怎么实现AspectJ方案里演示的对JSONObject
的反序列化监控呢?只需要将JSONObject
对应的构造函数替换成我们的函数
@Override
void visitMethodInsn(int opcode, String owner, String methodName, String descriptor, boolean isInterface) {
if(opcode == Opcodes.INVOKESPECIAL && owner.equals(“org/json/JSONObject”)
&& methodName.equals(“”) && descriptor.equals(“(Ljava/lang/String;)V”)) {
super.visitMethodInsn(Opcodes.INVOKESTATIC, “com.example.apm.JSONObjectAgent”,
“init”, “(Ljava/lang/String;)Lorg/json/JSONObject;”, false);
} else {
super.visitMethodInsn(opcode, owner, methodName, descriptor, isInterface)
}
}
插桩的问题已经解决了!
Oh, we have solved one problem.
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
文件夹有以下学习笔记,自行下载!
-
BAT大厂面试题、独家面试工具包,
-
资料免费领取,包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
5tTD-1710699788226)]
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
文件夹有以下学习笔记,自行下载!
-
BAT大厂面试题、独家面试工具包,
-
资料免费领取,包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
[外链图片转存中…(img-TzpU49nq-1710699788227)]