APM框架Matrix源码分析(八)字节码插桩之扩展网络监控

128 阅读3分钟

本篇文章主要介绍如何基于ASM + OkHttp3 + 自定义拦截器来实现网络请求的监控

1.插桩位置

我们需要我们自定义监控网络Interceptor插入到合适的位置。先来分析下拦截器的处理流程:

//RealCall.java
private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  //自定义拦截器
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));
  //链条对象Chain
  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  //执行下一个拦截器
  return chain.proceed(originalRequest);
}

interceptor.png

通过五大拦截器完成整个请求过程,责任链模式—> Request请求,经过5个拦截器,发送到最下边返回响应Response,再发送到最上边。这样自定义的拦截器既可以拿到请求数据,又可以拿到响应数据。

我们需要把自定义的拦截器插到client.interceptors()

public class OkHttpClient implements Cloneable, Call.Factory {
  //OkHttpClient中的成员变量interceptors,用来存放自定义拦截器
  final List<Interceptor> interceptors;
  //这个就是上述client.interceptors(),是OkHttpClient中的成员变量interceptors
  public List<Interceptor> interceptors() {
    return interceptors;
  }
  //OkHttpClient构造函数
  public OkHttpClient() {
    this(new Builder());
  }
	
  private OkHttpClient(Builder builder) {
    //传入了Builder的成员变量interceptors
    this.interceptors = Util.immutableList(builder.interceptors);
    //...
  }
  
  public static final class Builder {
    //Builder的成员变量interceptors
    final List<Interceptor> interceptors = new ArrayList<>();
    
    public Builder() {
      //...
      //插桩点:往interceptors中添加自定义拦截器
    }
    
    Builder(OkHttpClient okHttpClient) {
      //...
      //插桩点:往interceptors中添加自定义拦截器
    }
    
  }
}

client.interceptors()方法返回了OkHttpClient中的成员变量interceptors(用来存放自定义拦截器),OkHttpClient构造的时候被传入了Builder的成员变量interceptors,所有我们只需要在Builder的两个构造函数的最后一行把自定义拦截器添加到Builder的成员变量interceptors即可。

2.自定义监控网络Interceptor

public class NetWorkInterceptor implements Interceptor {
  
  @Override
  public Response intercept(Chain chain) throws IOException {
      //记录开始时间
      long startNs = System.currentTimeMillis();
      //从链表缓冲池中取OkHttpDataReplay(存每个网络请求的信息),没有则new一个
      replay = OkHttpDataReplay.create()
      //记录开始时间
      replay.startNs = startNs;
      Request request = chain.request();
      //记录请求数据,如url、上行数据大小等
      recordRequest(request);
    	
      Response response;
      try {
        response = chain.proceed(request);
      } catch (IOException e) {
        throw e;
      }
      //记录耗时
      replay.costTime = System.currentTimeMillis() - startNs;
      //记录响应数据,如响应码,下行数据大小等
      recordResponse(response);
      //收集数据分析
      doReplayCopy(replay);
      //处理完成回收,用于复用
      replay.recycle();
  }
}

通过自定义拦截器的方式,记录请求数据和响应数据。

为了减少字节码操作,定义一个工具类协助AMS进行代码插入,传入Builder的成员变量interceptors,判断自定义监控网络Interceptor是否已经添加过,没有则添加:

public class OkHttpUtils {
    public static void insertToOkHttpClientBuilder(List<Interceptor> interceptors) {
        try {
            //判断是否插入过自定义监控网络Interceptor
            boolean hasAddNetWorkInterceptor = false;
            for (Interceptor interceptor : interceptors) {
                if (interceptor instanceof NetWorkInterceptor) {
                    hasAddNetWorkInterceptor = true;
                    break;
                }
            }
            //没有则插入,避免重复
            if (!hasAddNetWorkInterceptor) {
                interceptors.add(new NetWorkInterceptor());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.插桩

先要判断当前访问的类是不是OkHttpClient的内部类Builder,接着使用Okhttp3MethodAdapter去处理插桩

@Override
public MethodVisitor visitMethod(int access, String name, String desc,
                                 String signature, String[] exceptions) {
    MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
    //判断当前访问的类是不是OkHttpClient的内部类Builder
    if ("okhttp3/OkHttpClient\$Builder".equals(className)) {
      //返回一个Okhttp3MethodAdapter去处理插桩
      return new Okhttp3MethodAdapter(name, api, access, desc, methodVisitor);
    }
    return super.visitMethod(access, name, desc, signature, exceptions);
}

Okhttp3MethodAdapter

class Okhttp3MethodAdapter(private val methodName: String, api: Int, access: Int, private val desc: String, mv: MethodVisitor?) : LocalVariablesSorter(api, access, desc, mv) {

    override fun visitInsn(opcode: Int) {
        //在okhttp3.OkHttpClient$Builder构造函数的最后一行插入insertToOkHttpClientBuilder
        if (isReturn(opcode) && isOkhttpClientBuild(methodName, desc)) {
            //加载this
            mv.visitVarInsn(ALOAD, 0)
            //访问Builder的interceptors
            mv.visitFieldInsn(GETFIELD, "okhttp3/OkHttpClient\$Builder", "interceptors", "Ljava/util/List;")
            //调用OkHttpUtils的静态方法insertToOkHttpClientBuilder
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tencent/matrix/trace/okhttp3/OkHttpUtils", "insertToOkHttpClientBuilder", "(Ljava/util/List;)V", false)
        }
        super.visitInsn(opcode)
    }

    private fun isReturn(opcode: Int): Boolean {
        return ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW)
    }
  
  private fun isOkhttpClientBuild(methodName: String, methodDesc: String): Boolean {
            return ("<init>" == methodName && ("()V" == methodDesc || "(Lokhttp3/OkHttpClient;)V" == methodDesc))
        }
}

也可以继承AdviceAdapter在方法出口onMethodExit插入

本篇到此结束