在前文中已经详细介绍了APM的android端的原理,接下来会通过代码实现记录某类异常日志这个小功能来深入理解APM的实现原理。场景如下,记录所有捕获的IndexOutOfBoundsException。前文中提到,APM一般分为3个部分,plugin、agent和具体的业务代码。本文也将会按这三个分类来介绍。
注:由于篇幅有限,本文所展示的只有部分关键代码,有兴趣的可自行阅读github上的源码。
- 业务代码
我们的业务场景很简单,只需提供一个处理异常的方法就足够了。
public static void pushException(Throwable th){
//在这里处理异常,如打印或上传日志
}
- agent
agent是最复杂的一个部分。它最终要达到的目的就是改写dexer.Main,在它执行processClass方法内的代码之前通过ASM工具修改第二个参数,即源class文件的byte数组。如果这个class的某个方法中包含了捕获IndexOutOfBoundsException的try-catch代码块,我们将在catch内调用上面的pushException方法。然后将这个修改过后的类对应的byte数组再替换回去。
首先说明,字节码是通过异常表来处理异常的,有兴趣的可以通过字节码查看工具来查看异常表长什么样子。大概就是表里面每行记录都定义了如果代码在start行到end行之间抛出了异常,那么将转到handle行进行处理。这里的start到end就相当于try到catch之间的代码,而handle就是catch内开始的代码。查看ASM文档,AdviceAdapter中的visitTryCatchBlock和visitLabel这两个方法正好能满足我们的需求。只需要在visitTryCatchBlock方法中记录目标exception处理的handle,然后,如果在visitLabel中传入的正是我们刚才记录的handle,则加上调用pushException方法的代码。
public class ExceptionLogMethodAdapter extends AdviceAdapter {
private TransformContext context;
//记录所有目标exception的handle
//key为handle,value是此handle对应的exception。
//注:一个catch可能包含了多个exception,
//如catch(IndexOutOfBoundsException | Exception e)
private HashMap<Label, ArrayList<String>> matchedHandle = new HashMap<>();
protected ExceptionLogMethodAdapter(TransformContext context
, MethodVisitor methodVisitor, int access, String name, String desc) {
super(Opcodes.ASM5, methodVisitor, access, name, desc);
this.context = context;
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handle,
String exception) {
//目标exception,在本文中为java/lang/IndexOutOfBoundsException
HashSet<String> targetException = context.getExceptions();
if (exception != null && targetException.contains(exception)) {
context.getLog().d("find exception " + exception);
ArrayList<String> handles = matchedHandle.get(handle);
if