本篇文章主要介绍如何基于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);
}
通过五大拦截器完成整个请求过程,责任链模式—> 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插入
本篇到此结束