在使用okhttp时,我们可能需要获取到okhttp的log日志,请求参数以及响应参数和数据。我们用一个小的demo来展示一下:
OkHttpClient client;
void initOkhttpClient() {
client = new OkHttpClient.Builder()
.addInterceptor(new LoggerInterceptor())
.build();
}
void interceptorTest() {
Map map = new HashMap();
map.put("commodityType", "01");
Gson gson = new Gson();
String json = gson.toJson(map);
final Request request = new Request.Builder()
.url("http://192.168.32.77:8089/api/commodity/getCommodityList")
.post(RequestBody.create(MediaType.parse("application/json"), json))
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
}
LoggerInterceptor
类具体如下:
public class LoggerInterceptor implements Interceptor {
public static final String TAG = "OkHttp日志";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
printRequestMessage(request);
Response response = chain.proceed(request);
printResponseMessage(response);
return response;
}
/**
* 打印请求消息
*
* @param request 请求的对象
*/
private void printRequestMessage(Request request) {
if (request == null) {
return;
}
Log.e(TAG, "Url : " + request.url().url().toString());
Log.e(TAG, "Method: " + request.method());
Log.e(TAG, "Heads : " + request.headers());
RequestBody requestBody = request.body();
if (requestBody == null) {
return;
}
try {
Buffer bufferedSink = new Buffer();
requestBody.writeTo(bufferedSink);
Charset charset = requestBody.contentType().charset();
charset = charset == null ? Charset.forName("utf-8") : charset;
Log.e(TAG, "Params: " + bufferedSink.readString(charset));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打印返回消息
*
* @param response 返回的对象
*/
private void printResponseMessage(Response response) {
if (response == null) {
return;
}
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
BufferedSource source = responseBody.source();
try {
source.request(Long.MAX_VALUE); // Buffer the entire body.
} catch (IOException e) {
e.printStackTrace();
}
Buffer buffer = source.buffer();
Charset charset = Util.UTF_8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(Charset.forName("utf-8"));
}
if (contentLength != 0) {
String result = buffer.clone().readString(charset);
Log.e(TAG, "头信息: " + response.headers());
Log.e(TAG, "body: " + result);
}
}
}
拦截器的原理,也就是说,拦截器是如何产生作用的?上面已经讲解了如何添加一个拦截器,这一节,我首先来讲解调用拦截器的流程:
在执行
Call call = client.newCall(request);
其实是new
了一个RealCall
,打开RealCall.enqueue()
:
public void enqueue(Callback responseCallback) {
...
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
这里调用到了调度器Dispatcher.enqueue()
方法,来看下这个方法:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
假设满足队列要求,这里会将AsyncCall
对象以任务的形式提交到线程池中,相当于一个任务就开始执行了。而AsyncCall
是RealCall
的一个内部类,且继承于NameRunnable
,NameRunnable.run()
中有一个抽象方法供RealCall
实现:
public abstract class NamedRunnable implements Runnable {
...
@Override public final void run() {
...
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
我们再来看RealCall.execute()
:
@Override
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
参数responseCallback
就是我们在外面new
的Callback
实例,关于拦截器的就是这一句代码了:
Response response = getResponseWithInterceptorChain();
我们继续往下看:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//开发人员自定义的拦截器全部添加到interceptors对象中
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));
//new了一个拦截器实例
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//这句代码是重点,循环+递归
return chain.proceed(originalRequest);
}
接下来看下拦截器链RealInterceptorChain
的chain.proceed()
:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
......
// Call the next interceptor in the chain.
//这里重点看两个参数,整型的`index`和集合对象`interceptors`,假设索引`index`刚开始为0,而`RealInterceptorChain next = new RealInterceptorChain(interceptors, ..., index + 1, ...);`表示`new`了一个索引`index`为1的拦截器链
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//当index为0时,获取到第一个拦截器
Interceptor interceptor = interceptors.get(index);
//以(index+1)下一个拦截器链为参数,执行上一个拦截器的拦截方法intercept()。
Response response = interceptor.intercept(next);
......
return response;
}
拦截器的核心原理,在于对循环递归的理解,我将递归流程图画出来了,如下:
至此,拦截器的原理就已分析完。
拦截器的话,主要就是看里面的intercept()
方法啦
RetryAndFollowUpInterceptor
拦截器中的intercept()
是一个死循环:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
......
Response priorResponse = null;
while (true) {
......
response = realChain.proceed(request, streamAllocation, null, null);
......
Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
......
}
}
我将部分代码省略掉了,大致逻辑是,如果followUp
实例为null
,表示不需要进行重试策略,这时候直接返回response
,否则需要重试策略,重试需要对资源的释放和复用处理。
桥接主要是对请求头和响应头的处理了,添加一些默认的请求头。
只支持get请求,需要服务器配置,控制相应的header
响应头。
获取到具体的连接器RealConnection
,跟服务器进行连接。
通道已经连接好,进行数据交互。
参考文章
关于Okhttp3-CacheInterceptor
OkHttp深入理解-ConnectInterceptor
OKhttp源码学习