安卓進階(7)之OkHttp3.10攔截器原理解析
部落格流程
- 用一個demo介紹如何新增自定義的攔截器;
- 介紹攔截器是怎麼產生攔截效果的;
- 介紹okhttp裡預設的各個攔截器的作用。
新增自定義的log攔截器
在使用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
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
,否則需要重試策略,重試需要對資源的釋放和複用處理。
橋接攔截器BridgeInterceptor
橋接主要是對請求頭和響應頭的處理了,新增一些預設的請求頭。
快取攔截器CacheInterceptor
只支援get請求,需要伺服器配置,控制相應的header
響應頭。
連線攔截器ConnectInterceptor
獲取到具體的聯結器RealConnection
,跟伺服器進行連線。
呼叫服務攔截器CallServerInterceptor
通道已經連線好,進行資料互動。