本文來探究一下 OkHttp3 的源碼和其中的設計思想。編程
關於 OkHttp3 的源碼分析的文章挺多,不過大多仍是在爲了源碼而源碼。我的以爲若是讀源碼不去分析源碼背後的設計模式或設計思想,那麼讀源碼的意義不大。 同時,若是熟悉的設計模式越多,那麼讀某個框架的源碼的時候就越容易,二者是相輔相成的,這也是許多大牛認爲多讀源碼能提升編程能力的緣由。設計模式
爲了方面後面的理解,我這裏簡單畫了個架構圖,圖中畫出了 OkHttp3 核心的功能模塊。爲了方便總體理解,這裏分了三個層次: 客戶層、執行層和鏈接層。
首先,客戶層的OkHttpClient ,使用過 OkHttp 網絡庫的同窗應該都熟悉,在發送網絡請求,執行層決定怎麼處理請求,好比同步仍是異步,同步請求的話直接在當前線程完成請求, 請求要通過多層攔截器處理; 若是是異步處理,須要 Dispatcher 執行分發策略, 線程池管理執行任務; 又好比,一個請求下來,要不要走緩存,若是不走緩存,進行網絡請求。最後執行層將從鏈接層進行網絡 IO 獲取數據。緩存
使用過 OkHttp 網絡庫的同窗應該都熟悉 OkHttpClient , 許多第三方框架都會提供一個相似的類做爲客戶訪問的一個入口。 關於 OkHttpClient 代碼註釋上就說的很清楚:bash
/** * Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their * responses. * * <h3>OkHttpClients should be shared</h3> * * <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for * all of your HTTP calls. This is because each client holds its own connection pool and thread * pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a * client for each request wastes resources on idle pools. * * <p>Use {@code new OkHttpClient()} to create a shared instance with the default settings: * <pre> {@code * * // The singleton HTTP client. * public final OkHttpClient client = new OkHttpClient(); * }</pre> * * <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings: * <pre> {@code * * // The singleton HTTP client. * public final OkHttpClient client = new OkHttpClient.Builder() * .addInterceptor(new HttpLoggingInterceptor()) * .cache(new Cache(cacheDir, cacheSize)) * .build(); * }</pre> * .... 省略*/複製代碼
簡單提煉:
一、OkHttpClient, 能夠經過 new OkHttpClient() 或 new OkHttpClient.Builder() 來建立對象, 可是—特別注意, OkHttpClient() 對象最好是共享的, 建議使用單例模式建立。 由於每一個 OkHttpClient 對象都管理本身獨有的線程池和鏈接池。 這一點不少同窗,甚至在我經歷的團隊中就有人踩過坑, 每個請求都建立一個 OkHttpClient 致使內存爆掉。服務器
二、 從上面的總體框架圖,其實執行層有不少屬性功能是須要OkHttpClient 來制定,例如緩存、線程池、攔截器等。若是你是設計者你會怎樣設計 OkHttpClient ? 建造者模式,OkHttpClient 比較複雜, 太多屬性, 並且客戶的組合需求多樣化, 這種狀況下就考慮使用建造者模式。 new OkHttpClien() 建立對象, 內部默認指定了不少屬性:cookie
public OkHttpClient() { this(new Builder());}複製代碼
在看看 new Builder() 的默認實現:網絡
public Builder() { dispatcher = new Dispatcher(); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); cookieJar = CookieJar.NO_COOKIES; socketFactory = SocketFactory.getDefault(); hostnameVerifier = OkHostnameVerifier.INSTANCE; certificatePinner = CertificatePinner.DEFAULT; proxyAuthenticator = Authenticator.NONE; authenticator = Authenticator.NONE; connectionPool = new ConnectionPool(); dns = Dns.SYSTEM; followSslRedirects = true; followRedirects = true; retryOnConnectionFailure = true; connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0;}複製代碼
默認指定 Dispatcher (管理線程池)、連接池、超時時間等。架構
三、 內部對於線程池、連接池管理有默認的管理策略,例如空閒時候的線程池、鏈接池會在必定時間自動釋放,但若是你想主動去釋放也能夠經過客戶層去釋放。(不多)app
Response response = mOkHttpClient.newCall(request).execute();
複製代碼
這是應用程序中發起網絡請求最頂端的調用,newCall(request) 方法返回 RealCall 對象。RealCall 封裝了一個 request 表明一個請求調用任務,RealCall 有兩個重要的方法 execute() 和 enqueue(Callback responseCallback)。 execute() 是直接在當前線程執行請求,enqueue(Callback responseCallback) 是將當前任務加到任務隊列中,執行異步請求。框架
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { // client.dispatcher().executed(this) 內部只是記錄下執行狀態, client.dispatcher().executed(this); // 真正執行發生在這裏 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { // 後面再解釋 client.dispatcher().finished(this); }}複製代碼
執行方法關鍵在 getResponseWithInterceptorChain() 這個方法中, 關於 client.dispatcher().executed(this) 和 client.dispatcher().finished(this); 這裏先忽略 ,後面再看。
請求過程要從執行層說到鏈接層,涉及到 getResponseWithInterceptorChain 方法中組織的各個攔截器的執行過程,內容比較多,後面章節在說。先說說 RealCall 中 enqueue(Callback responseCallback) 方法涉及的異步請求和線程池。
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true;} captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback));}複製代碼
調用了 dispatcher 的 enqueue()方法
dispatcher 結合線程池完成了全部異步請求任務的調配。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
dispatcher 主要維護了三兩個隊列 readyAsyncCalls、runningAsyncCalls 和 runningSyncCalls,分別表明了準備中隊列, 正在執行的異步任務隊列和正在執行的同步隊列, 重點關注下前面兩個。
如今咱們能夠回頭來看看前面 RealCall 方法 client.dispatcher().finished(this) 這個疑點了。
在每一個任務執行完以後要回調 client.dispatcher().finished(this) 方法, 主要是要將當前任務從 runningAsyncCalls 或 runningSyncCalls 中移除, 同時把 readyAsyncCalls 的任務調度到 runningAsyncCalls 中並執行。
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }複製代碼
默認實現是一個不限容量的線程池 , 線程空閒時存活時間爲 60 秒。線程池實現了對象複用,下降線程建立開銷,從設計模式上來說,使用了享元模式。
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);return chain.proceed(originalRequest); }}複製代碼
要跟蹤 Okhttp3 的網絡請求任務執行過程 ,須要看懂以上代碼,看懂以上代碼必須理解設計模式-責任鏈。在責任鏈模式裏,不少對象由每個對象對其下家的引用而鏈接起來造成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織和分配責任。 網絡請求過程,是比較典型的複合責任鏈的場景,好比請求傳遞過程,咱們須要作請求重試, 須要執行緩存策略, 須要創建鏈接等, 每個處理節點能夠由一個鏈上的對象來處理; 同時客戶端使用的時候可能也會在請求過程當中作一些應用層須要的事情,好比我要記錄網絡請求的耗時、日誌等, 責任鏈還能夠動態的擴展到客戶業務方。
在 OkHttp3 的攔截器鏈中, 內置了5個默認的攔截器,分別用於重試、請求對象轉換、緩存、連接、網絡讀寫。
以上方法中先是添加了客戶端自定義的鏈接器,而後在分別添加內置攔截器。
如今咱們把對 OkHttp 網絡請求執行過程的研究轉化對每一個攔截器處理的研究。
retryAndFollowUpInterceptor 處於內置攔截器鏈的最頂端,在一個循環中執行重試過程:
一、首先下游攔截器在處理網絡請求過程如拋出異常,則經過必定的機制判斷一下當前連接是否可恢復的(例如,異常是否是致命的、有沒有更多的線路能夠嘗試等),若是可恢復則重試,不然跳出循環。
二、 若是沒什麼異常則校驗下返回狀態、代理鑑權、重定向等,若是須要重定向則繼續,不然直接跳出循環返回結果。
三、 若是重定向,則要判斷下是否已經達到最大可重定向次數, 達到則拋出異常,跳出循環。
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 建立鏈接池管理對象
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 將請求處理傳遞下游攔截器處理
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 線路異常,判斷知足可恢復條件,知足則繼續循環重試
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// IO異常,判斷知足可恢復條件,知足則繼續循環重試
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request))
throw e;releaseConnection = false;continue;
} finally {// We’re throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);streamAllocation.release();
}
// Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) { response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); } // 是否須要重定向 Request followUp = followUpRequest(response); if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } // 不須要重定向,正常返回結果 return response; } closeQuietly(response.body()); if (++followUpCount > MAX_FOLLOW_UPS) { // 達到次數限制 streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(followUp.url()), callStackTrace); } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } request = followUp; priorResponse = response; }}複製代碼
/** * Bridges from application code to network code. First it builds a network request from a user * request. Then it proceeds to call the network. Finally it builds a user response from the network * response. */複製代碼
這個攔截器比較簡單, 一個實現應用層和網絡層直接的數據格式編碼的橋。 第一: 把應用層客戶端傳過來的請求對象轉換爲 Http 網絡協議所需字段的請求對象。 第二, 把下游網絡請求結果轉換爲應用層客戶所須要的響應對象。 這個設計思想來自適配器設計模式,你們能夠去體會一下。
CacheInterceptor 實現了數據的選擇策略, 來自網絡仍是來自本地? 這個場景也是比較契合策略模式場景, CacheInterceptor 須要一個策略提供者提供它一個策略(錦囊), CacheInterceptor 根據這個策略去選擇走網絡數據仍是本地緩存。
緩存的策略過程:
一、 請求頭包含 「If-Modified-Since」 或 「If-None-Match」 暫時不走緩存
二、 客戶端經過 cacheControl 指定了無緩存,不走緩存
三、客戶端經過 cacheControl 指定了緩存,則看緩存過時時間,符合要求走緩存。
四、 若是走了網絡請求,響應狀態碼爲 304(只有客戶端請求頭包含 「If-Modified-Since」 或 「If-None-Match」 ,服務器數據沒變化的話會返回304狀態碼,不會返回響應內容), 表示客戶端繼續用緩存。
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null? cache.get(chain.request()):
null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 獲取緩存策略
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
// The cache candidate wasn’t applicable. Close it.
}
// If we’re forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1).code(504).message(「Unsatisfiable Request (only-if-cached)」).body(Util.EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(System.currentTimeMillis()).build();
}
// 走緩存
if (networkRequest == null) {
return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();}Response networkResponse = null;
try {
// 執行網絡
networkResponse = chain.proceed(networkRequest);
} finally {
// If we’re crashing on I/O or otherwise, don’t leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 返回 304 仍然走本地緩存
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit();
cache.update(cacheResponse, response); return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (
HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 存儲緩存
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
} if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
緩存實現
OkHttp3 內部緩存默認實現是使用的 DiskLruCache, 這部分代碼有點繞:
interceptors.add(new CacheInterceptor(client.internalCache()));
初始化 CacheInterceptor 時候 client.internalCache() 這裏獲取OkHttpClient的緩存。
InternalCache internalCache() { return cache != null ? cache.internalCache : internalCache;}複製代碼
注意到, 這個方法是非公開的。 客戶端只能經過 OkhttpClient.Builder的 cache(cache) 定義緩存, cache 是一個 Cache 對實例。 在看看 Cache 的內部實現, 內部有一個 InternalCache 的內部類實現。 內部調用時使用 InternalCache 實例提供接口,而存儲邏輯在 Cache 中實現。
Cache 爲何不直接實現 InternalCache ,而經過持有 InternalCache 的一個內部類對象來實現方法? 是但願控制緩存實現, 不但願用戶外部去實現緩存,同時對內保持必定的擴展。
RealCall 封裝了請求過程, 組織了用戶和內置攔截器,其中內置攔截器 retryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor 完執行層的大部分邏輯 ,ConnectInterceptor -> CallServerInterceptor 兩個攔截器開始邁向鏈接層最終完成網絡請求。
喜歡的關注下筆者,順便點個讚唄。