開篇陳述:OkHttp做爲一個優秀的網絡請求庫,陪咱們這些Android開發佬走過了許多風風雨雨的夜晚,因此今天特寫一篇深刻理解做文。整個系列篇中,在okhttp3.14.0版本上,依照OkHttp的使用爲引線,作結構以下剖析:java
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
//見【2.2】client.newCall()
//見【2.3】RealCall.execute()
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
複製代碼
如上是一次同步的網絡請求,主要作以下事情:web
OkHttpClient.java
@Override public Call newCall(Request request) {
//直接調用Realcall的方法,新建了一個RealCall對象。
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//這裏須要特別注意OkHttpClient對象被Call持有。
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
}
複製代碼
如上所示,OkHttpClient.newCall() 的調用鏈最終構建了一個 RealCall 對象,而且把client做爲 RealCall 的成員變量,方便後續請求從 client 獲取配置。json
RealCall.java
@Override public Response execute() throws IOException {
synchronized (this) {
// 若是該請求已經執行過,報錯。
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
//見【2.4】獲取 client 裏面的調度器 Dispatcher 並記錄這個請求。
client.dispatcher().executed(this);
//見【2.5】經過責任鏈的方式將發起請求並返回結果。這裏是同步動做,會阻塞。
return getResponseWithInterceptorChain();
} finally {
//請求完後須要把這個請求從調度器中移走
client.dispatcher().finished(this);
}
}
複製代碼
Call在被執行時作以下事情:設計模式
Dispatcher.java
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); synchronized void executed(RealCall call) { runningSyncCalls.add(call); } 複製代碼
就是將這次請求加入到一個雙端隊列數據集合中。緩存
Response getResponseWithInterceptorChain() throws IOException {
// 將請求的具體邏輯進行分層,並採用責任鏈的方式進行構造。
List<Interceptor> interceptors = new ArrayList<>();
// 用戶自已的請求攔截器
interceptors.addAll(client.interceptors());
//重試和重定向攔截器
interceptors.add(new RetryAndFollowUpInterceptor(client));
//橋攔截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//緩存邏輯攔截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//網絡鏈接邏輯攔截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
// 網絡請求攔截器,真正網絡通行的地方,這個攔截器處理事後會生成一個Response
interceptors.add(new CallServerInterceptor(forWebSocket));
//依照如上配置,構建出一個請求的處理邏輯責任鏈,特別注意:這條鏈開始於下標位置爲的0攔截器。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//詳見【2.6】,按下處理邏輯鏈條的開關。
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
//返回請求結果
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
複製代碼
經過該方法的名字咱們也能夠知道,這個方法就是經過攔截器鏈來得到Response的過程。他作了以下事情:bash
【2.6】RealInterceptorChain.proceed()cookie
RealInterceptorChain.java
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
/**
* index+1:構建出新的攔截鏈,不過新的攔截鏈的處理攔截器是下標爲index+1的
* 實現了責任鏈中,處理邏輯的流轉。
*/
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
//此時index = 0;因此拿到了第一個攔截器,而且調用他的intercept 方法進行具體邏輯處理。
Interceptor interceptor = interceptors.get(index);
//當前攔截器對網絡請求進行處理。
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// 省略對response合法性的檢查代碼
...
return response;
}
複製代碼
總結:網絡
3.1app
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
//client.newCall():同【2.2】
//詳見【3.2】RealCall.enqueue()
try (Response response = client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
}) {
return response.body().string();
}
}
複製代碼
如上是一次OkHttp的異步請求使用方法,基本於【2.1】的同步請求一致,惟一不一樣的是,call的異步調用是經過RealCall.enqueue()實現的。而請求結果經過Callback回調到主線程。異步
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
//詳見【3.4】:AsyncCall()
//詳見【3.5】:Dispatcher.enqueue()
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
將用戶建立的callback做爲參數傳入AsyncCall()構造函數。AsyncCall 繼承於Runnable.
AsyncCall.java
final class AsyncCall extends NamedRunnable {
private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
...
/**
* 該方法是在dispather須要執行此請求的時候,分配給它線程池,此異步請求便在這個線程池中執行網絡請求。
*/
void executeOn(ExecutorService executorService) {
...
boolean success = false;
try {
//異步的關鍵:將請求放到線程池中執行。
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
...
success = false;
} finally {
if (!success) {
client.dispatcher().finished(this); // 執行失敗會經過Dispatcher進行finished,之後不再會用此AsyncCall。
}
}
}
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
//此處同【2.5】
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
//請求成功時,回調Response給到用戶
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
...
//請求錯誤時,回調錯誤接口給到用戶
responseCallback.onFailure(RealCall.this, e);
} finally {
//詳見【3.6】,結束一次請求。
client.dispatcher().finished(this);
}
}
}
複製代碼
從上面能夠看出,AsyncCall繼承於Runnable,它提供了將Call放到線程池執行的能力,實現了請求的異步流程。
Dispatcher.java
//準備進行異步調用的請求。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在執行的異步請求。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
void enqueue(AsyncCall call) {
synchronized (this) {
//將異步請求加入到雙端隊列中
readyAsyncCalls.add(call);
// 尋找是否有同Host的請求,若是有進行復用
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
//【詳見3.7】將符合條件的Ready的異步請求轉入runningAsyncCalls,並執行
promoteAndExecute();
}
複製代碼
總結:
Dipatcher.java
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
...
//【詳見3.7】一個請求完成後,檢查此時是否有在等待執行的請求,並處理。
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
//通知此時已經沒有異步請求任務
idleCallback.run();
}
}
複製代碼
總結:調度器結束一次請求
private boolean promoteAndExecute() {
...
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
//檢查最大請求數限制和
if (runningAsyncCalls.size() >= maxRequests) break;
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;
//知足條件,便把預備隊的請求提高到執行隊列。
i.remove();
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
//將可執行的異步請求放進線程池執行
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
//【詳見3.4】
asyncCall.executeOn(executorService());
}
return isRunning;
![](https://user-gold-cdn.xitu.io/2020/6/21/172d4b4a7205d520?w=444&h=446&f=png&s=28383)
}
複製代碼
總結 :該方法是對預備隊列裏的請求提高至執行隊列並執行的一次嘗試。若是不能執行,他啓動時機將會延後到其餘請求結束(如【3.6】邏輯)。
小篇結:本篇中以Okhttp的用法爲主線,探究了它的同步請求、異步請求的代碼邏輯。而OkHttp最主要的設計模式:責任鏈模式也在其中有涉及到。最後,咱們經過2張圖片來理解同步和異步的整個請求流程(圖片不是本人畫的):
同步請求
複製代碼
異步請求流程複製代碼