在项目中记录或打印OkHttp网络请求,我们一般会通过添加HttpLoggingInterceptor到我们的OkHttpClient来实现。但在一个较大的APP中,有些业务模块是通过AAR依赖到项目中。对于这些模块的网络请求监控,我们可以直接将监控逻辑代码插桩到OkHttp源码中来实现。
通过查看OKhttp3源码,定位到需要将代码织入的位置为RealCall类的getResponseWithInterceptorChain()函数
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 (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
Response response = chain.proceed(originalRequest);
if (retryAndFollowUpInterceptor.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
//织入记录Response的函数
//recordOkHttp(response)
return response;
}
接下来实现插件的transform,在MethodVisitor中的onMethodExit函数编写织入代码逻辑:
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode)
//RealCall类在OkHttp3各版本中存在的包名不固定
//getResponseWithInterceptorChain函数的逻辑各版本也存在差异
if (className.endsWith('/RealCall') && nameDesc.startsWith('getResponseWithInterceptorChain')) {
if (opcode == ARETURN) {
mv.visitInsn(Opcodes.DUP);
methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "recordOkHttp", "(Lokhttp3/Response;)V", false)
}
}
}
编写完插件后,最后来实现recordOkHttp函数:
public static void recordOkHttp(Response response) {
try {
//屏蔽掉不需要记录的host
if (!ExcludeHosts.shouldRecord(response.request().url().host())) {
return;
}
//只记录特定content type
if(isIgnoreContentType(response.body().contentType().toString())){
return;
}
String request = response.request().toString();
HttpUrl httpUrl = response.request().url();
URL url = httpUrl.url();
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
//读取buffer之前要先执行clone
String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8"));
//构建保存接口信息的实体类
ResponseBean responseBean = new ResponseBean();
responseBean.url = url.toString();
responseBean.requestTime = response.sentRequestAtMillis();
//记录Post的参数
if("POST".equals(response.request().method()) && response.request().body() instanceof FormBody){
FormBody body = (FormBody) response.request().body();
StringBuffer sb = new StringBuffer("{");
for (int i = 0; i < body.size(); i++) {
sb.append(body.encodedName(i)).append(":").append(body.encodedValue(i));
if(i != body.size() - 1){
sb.append(",");
}
}
if(body.size() > 0){
responseBean.postParams = sb.append("}").toString();
}
}
responseBean.headers = response.request().headers().toString();
responseBean.response = responseBodyString;
responseBean.responseTime = response.receivedResponseAtMilli();;
//实现保存逻辑
} catch (Exception e) {
LogUtils.E(e);
}
}
通过上述的流程,我们就可以记录或打印以OkHttp进行的网络请求的信息了。