在《OkHttp深刻分析——基礎認知部分》對 OkHttp 源碼的工程已經有一個大體的瞭解了,這篇文章裏就來分析下核心模塊 OkHttp。首先看看代碼結構的框架圖。web
框架圖中能夠看到涉及到的東西很是多,也看到了不少協議相關的東西。前面的文章中有分析過 Volley——《Volley還算比較深刻的分析》,也分析過 Android 原生的網絡框架——《Android原生網絡庫HttpURLConnection分析——HTTP部分》《Android原生網絡庫HttpURLConnection分析——HTTPS部分》。經過將這 3 者對比不難發現,OkHttp 實際上是一個低級別的網絡框架,其和 Android 原生網絡庫 HttpURLConnection 是一個級別的,屬於 Http 協議層。它是徹底取代了系統的原生 Http 協議的實現,同時還加上了高度的抽象與封裝。而 Volley 只是一個更高度的抽象與封裝,其最大的目的在於使咱們使用 Http 更簡單,對 Http 的通訊實際上是沒有太大實質性的改進的。固然,你能夠指定網絡庫爲 OkHttp。數組
源碼分析以發起一個異步的 Get 請求爲主線進行分析,Post 請求以及同步方式請求就忽略了。掌握知識抓主幹,其餘的遇到問題再來解決問題。緩存
以下的 Demo 是從工程源碼 sample 中的 AsynchronousGet 中提出來的。安全
// 1. 構建 OkHttpClient
OkHttpClient client = new OkHttpClient();
// 2.構建一個 Request
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
// 3.使用 client 插入一個異步請求,在 Callback 中等待返回結果
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
// 4.請求成功後,返回結果會被封裝在 Response 中
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
複製代碼
Demo 中的代碼很簡單,總共分了 4 個步驟,這也是分析 OkHttp 的主線。bash
構建 OkHttpClient 的分析是很是重要的,它表明了框架的上下文,爲網絡請求的運行初始化了一切的環境。先來看一下它的類圖。服務器
屬性很是多,密密麻麻的。關於每一個屬性的意思,放在下面的 Builder 中一一講述。那再來看一下構造方法。cookie
public OkHttpClient() {
this(new Builder());
}
複製代碼
默認構建方法 new 了一個默認的 Builder,也就是一切規則都採用默認的。網絡
......
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
......
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
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;
callTimeout = 0;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
複製代碼
把它們用表格整理下再來看看,好像也還數得清的,共 25 個。app
序號 | 字段名 | 說明 |
---|---|---|
1 | dispatcher | 任務分發器 |
2 | protocols | 支持的協議集,默認爲 Http2 和 Http 1.1 |
3 | connectionSpecs | 主要是針對 https 的 socket 鏈接的配置項,包括在協商安全鏈接時使用的TLS版本和密碼套件。 |
4 | eventListenerFactory | 建立 EventListener 的工廠方法類,重點在於 EventListener,經過 EventListener 咱們能夠獲得更多有關 Http 通訊的可度量數據 ,如數量、大小和持續時間,以便於咱們更加詳細的統計網絡的情況。 |
5 | proxySelector | 代理選擇器,默認通常用系統的DefaultProxySelector。 |
6 | cookieJar | 定義瞭如何存儲或者讀取 cookies,若是不設置則不存儲 cookie。 |
7 | Cache | 緩存 Response 結果,若是不設置則爲 null |
8 | InternalCache | 內部緩存 |
9 | socketFactory | SocketFactory,主要就是定義如何建立 socket,默認是 DefaultSocketFactory。 |
10 | hostnameVerifier | HostnameVerifier 接口的實現,與證書校驗相關。在握手期間,若是通訊 URL 的主機名和服務器的標識主機名不匹配或者說不安全時,則底層的握手驗證機制會回調 HostnameVerifier 接口的實現程序來肯定是否應該容許此鏈接。 |
11 | certificatePinner | 證書鎖定,防止證書攻擊。典型的用例是防止代理工具抓包。 |
12 | authenticator | 受權相關,如著名的 401 返回碼。通常場景是在 token 過時的狀況下發生,但在實際開發中,大部分服務器不會這樣實現,而是正常返回在自定義碼裏面。 |
13 | proxyAuthenticator | 通過代理服務器的受權相關 |
14 | connectionPool | 鏈接池 |
15 | dns | DNS,沒有設置的話則爲系統的 DNS 列表 |
16 | followSslRedirects | 是否容許 SSL 重定向 |
17 | followRedirects | 容許重定向 |
18 | retryOnConnectionFailure | 容許失敗重連 |
19 | callTimeout | 調度超時時間 |
20 | connectTimeout | 鏈接超時時間 |
21 | readTimeout | 讀取超時時間 |
22 | writeTimeout | 寫入超時時間 |
23 | pingInterval | ping 的間隔時間 |
24 | interceptors | 攔截器 |
25 | networkInterceptors | 網絡攔截器 |
這裏挑出幾個重要的屬性做進一步的展開,其餘沒有展開的看看錶格里的說明便可。框架
先來看一看它的類圖。
executorService: 線程池調度器,能夠由外部指定。若是沒有指定,則有默認值。默認線程池的指定核心線程數爲 0,但最大線程數爲 Integer.MAX_VALUE,阻塞隊列是一個無容量的阻塞隊列,以及超出核心線程數的線程的存活時間爲 60 s。其特色是若是線程數量大於核心線程數,但小於等於最大線程數,且阻塞隊列是 SynchronousQueue 的時候,線程池會建立新線程來執行任務,由於 SynchronousQueue 是沒有容量的。這些線程屬於非核心線程,在任務完成後,閒置時間達到了超時時間就會被清除。線程同時是由一個線程工廠建立的,建立出來的線程名字爲 "OkHttp Dispatcher"。
readyAsyncCalls,runningAsyncCalls,runningSyncCalls: 分別是異步等待隊列,異步運行時隊列以及同步運行時隊列。它們的類型都是 ArrayDeque,該類是雙端隊列的實現類。
idleCallback: 也就是當 Dispatcher 中沒有任何任務執行時,也就是進入了 idle 狀態了,所執行的 runnable 類型的 Callback。
maxRequests 和 maxRequestsPerHost: maxRequests 也就是最大請求數,默認爲 64。而 maxRequestsPerHost 指的是以 url 中的域名來判斷的最大的 host 的數量,默認爲 5。
Dispatcher 就先了解到這裏了,後面在分析異步請求的過程當中還會講述它是如何進行調度的。
緩存 Cache 就是將 Response 結果存儲在磁盤上,而且配合 Http 協議的 Cache-controller 等規則對緩存結果進行增刪改查。也就是過時了就去請求網絡結果並更更新緩存或者刪除緩存;沒過時則就用緩存中的結果而不用去請求網絡結果,從而節省時間以及寬帶。
主要是用來管理HTTP和HTTP/2鏈接的重用,以減小網絡延遲。對於共享同一個 Address 的請求,其也會共享同一個鏈接。鏈接池裏的鏈接一直保持着鏈接,以待同一鏈接的其餘請求來複用。
這 2 個都是攔截器,咱們也均可以添加本身的攔截器以達到咱們本身的目的。其中 interceptors 也稱做 application interceptors,它主要發生在請求前和請求後這 2 個階段。而 network interceptors 則發生在請求中,即網絡實際通訊的過程當中。官方有一個圖來幫助咱們理解它們之間的做用以及關係。
OkHttpClient 的構建就是一個網絡通訊環境的初始化,細數下來其關鍵的幾個部分是任務分發器,緩存,鏈接池,攔截器。這裏先對它們有一個基礎的認知,後面還會再和他們一一碰面的。
Request 沒有提供默承認用的構造方法給咱們,必須用 Request.Builder 來進行構造。其類圖結構以下。
Request 看起來就簡單多了。下面就幾個重要的屬性作一個簡要的說明。
盜個圖吧,能說明一切的圖。
Headers 就是存放 Http 協議的頭部參數,通常來講是 Key-values 形式。但 Headers 的實際實現不是用 Map 之類的結構,而是用了一個 String 數組來存儲,而且規定了偶數爲 key,奇數爲 value。這樣的設計下明顯不論是時間上仍是空間上都要優於 Map 類的結構。固然,Headers 不只用於 Request,其還用於 Response。
RequestBody 實際上是用於 Post 請求的,也就是請求的主體內容。 主體內容能夠有許多種類,如文件,表單以及Json字串等等。RequestBody 由兩部分組成,即 content 以及用於描述 content 類型的 MediaType。
通常狀況下,經過 Charles 抓包能夠觀察到,一個 Get 請求的樣子一般以下所示,這是請求 www.baidu.com 的根目錄 / 。
構建好了 OkHttpClient 就有了基礎環境,構建好了 Request 知道了咱們要發送什麼出去。接下來就是提交請求,等待處理。那麼提交給誰呢?接下來一步一步分析。
先來過一遍時序圖。
首先經過 OkHttpClient 的 newCall 方法建立一個 RealCall,RealCall 實現自接口 Call。它的類圖結構以下。
newCall 方法比較簡單,它只是進一步經過 RealCall 的靜態方法 newRealCall 來實際建立一個 RealCall 實例。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
複製代碼
這個方法使得 RealCall 對象同時拿到 OkHttpClient,Request 以及 eventListener 3 個重要的引用,並記錄在其對象中。
而後下一步調用的就是 RealCall 的 enqueue() 方法,但注意這裏的參數是一個 Callback。看看它的實現。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
// 通知鏈接開始
eventListener.callStart(this);
// 向 OkHttpClient 的 Dispatcher 插入一個 AsyncCall
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製代碼
enqueue() 方法又進一步調用了 OkHttpClient 的屬性 Dispatcher 的 enqueue,而且還 new 了一個 AsyncCall 來做爲參數入隊。看一看 AsyncCall 的類圖結構。
AsyncCall 是一個 Runnable,這麼說來 AsyncCall 纔是 Dispatcher 就實際調度的 Runnable。進一步看看 Dispatcher 的 enqueue 方法。
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
複製代碼
先是將其添加到隊列 readyAsyncCalls 中去,而後調用 promoteAndExecute() 方法將 AsyncCall 從 readyAsyncCalls 移到 runningAsyncCalls 中去,並將 AsyncCall 交由 ExecutorService 來進行線程池調度。關於 Dispatcher 以及 ExecutorService 的特色已經在上面講過了。這裏就再也不贅述了。而調度其實是交給 AsyncCall 的 executeOn () 方法來發起的。
void executeOn(ExecutorService executorService) {
......
executorService.execute(this);
......
}
複製代碼
向 ExecutorService 提交了 AsyncCall ,下一步就是等待 AsyncCall 被調度到,而後完成進一步的工做,構建 Interceptor Chain。
一樣是先過一下時序圖。
若是 AsyncCall 被調度到的話,那麼就會調起它的 run() 方法。而它的 run() 方法又會調用它的 execute() 方法。
@Override protected void execute() {
.....
Response response = getResponseWithInterceptorChain();
......
}
複製代碼
這裏調用的是 RealCall 的 getResponseWithInterceptorChain() 方法,getResponseWithInterceptorChain() 方法執行完成後就獲得了最終的結果 Response。
看到這裏插入一段話,這裏總結一下 RealCall 與 AsyncCall 的關係,還真是有一點微妙。經過 OkHttpClient 建立了 RealCall 的時候將 Request 已經給 RealCall 來持有了,而後向 RealCall 的 enqueue 入隊時其是一個 Callback。而內部其實又建立了一個 AsyncCall 用來給 OkHttpClient 的 Dispatcher 來進行調度。也就是說 RealCall 纔是真正執行任務的,而 AsyncCall 是起到其中的調度做用。被調度起的 AsyncCall 又來驅動 RealCall 來執行任務。
而後再來看看 getResponseWithInterceptorChain() 的實現吧。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 用戶添加的 interceptor,注意此時請求還未開始。
interceptors.addAll(client.interceptors());
// 添加框架內所必須的 ****Interceptor
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 非 web socket 的狀況下,還會添加用戶所添加的 networkInterceptors。
interceptors.addAll(client.networkInterceptors());
}
// 最後添加 CallServerInterceptor,這是 Interceptor Chain 的終止符。什麼意思呢?後面還會再講到。
interceptors.add(new CallServerInterceptor(forWebSocket));
// 構建一個起始 RealInterceptorChain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
// 開始 Interceptor Chain 的執行以獲取 Response。
return chain.proceed(originalRequest);
}
複製代碼
這段構造 Interceptor Chain 的代碼看起來多,但實際上是很是簡單的。就是將全部的 Interceptor 包括用戶定義的以及框架所必須的按照順序組織成一個列表,並結合 RealInterceptorChain 來構造出 Interceptor Chain。這個 Interceptor Chain 實現的確實很精妙,但說實話它又被不少人所神話了,好像其很是難且難以想象同樣。咱們來進一步看看它的工做原理。
先來看看它的工做原理圖
從原理圖上能夠看出,在 Chain 的 proceed() 方法中調用了 Interceptor 的 intercepter() 方法,而在 Interceptor 的 intercepter() 方法中又調用了 Chain 的 proceed() 方法。這是 2 個方法組成的遞歸調用,入口是 getResponseWithInterceptorChain() 中所 new 出來的 Chain,而出口是 CallServerInterceptor。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
......
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
......
return response;
}
複製代碼
把該方法精簡單了一下,核心代碼就上面那 3 句。先構造出 next Chain,注意其中的 index + 1,也就是說下一個 Chain 得調用下一個 Interceptor。這裏先獲取了當前 index 的 Interceptor 的 interceptor 方法。注意這裏傳遞了參數 next Chain,告訴 Interceptor 應該在執行完後就調用 Chain 的 proceed 繼續處理,直到遇到 CallServerInterceptor 再也不調用 Chain 的 proceed 了,從而終結 Intercepter Chain 的執行。
Intercepter Chain 執行起來了,下面就該獲取其結果了。
獲取 Response 的過程,就是 Intercepter Chain 的執行過程,而進一步精簡地說,就是 Interceptor 的執行。這裏假設沒有用戶所添加的 Interceptor,那接下來要分析的就是框架內的那 5 個重要的 Interceptor。也就是 getResponseWithInterceptorChain 中所添加的 RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor 以及 CallServerInterceptor。而它們的執行順序就是它們的添加順序。
而在分析以前,先來看看這個要獲取的 Response 到底是什麼?
相對於 Request 來講,Response 顯然要內容上要多得多。code,protocol以及message 就是請求行。headers 與 Request 中的 headers 含義上是同樣的。handshake 主要是用於握手的。body 就是它的實際內容。
通常狀況下,經過 Charles 抓包可獲取以下圖所示的結果。
對於每個 Interceptor 而言,只要從它的 intercept() 方法開始分析就能夠了。
@Override public Response intercept(Chain chain) throws IOException {
// 獲取 Request
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 獲取 RealCall
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 建立 StreamAllocation
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
// 記錄請求的次數,包括正常,重試,重定向以及認證的次數
int followUpCount = 0;
Response priorResponse = null;
// 經過循環體來實現失敗後的重試或者重定向等
while (true) {
......
Response response;
......
response = realChain.proceed(request, streamAllocation, null, null);
......
Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
streamAllocation.release();
return response;
}
// 達到次數上限了
if (++followUpCount > MAX_FOLLOW_UPS) {
......
}
......
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
}
request = followUp;
priorResponse = response;
}
}
複製代碼
用流程圖簡化一下上面的邏輯,以下所示。
從上面的實現來看,其主要是用了一個循環體來控制,若是請求沒有成功就會再次執行 Chain.proceed() 方法,以再次運行起 Interceptor Chain。而循環的次數由 MAX_FOLLOW_UPS 來決定,其默認大小是 20。
而是否成功的檢查都是經過 followUpRequest 來進行的。從上面代碼也能夠看到,若是它返回 null 則說明請求是成功的,不然確定是出了其餘「異常」。
private Request followUpRequest(Response userResponse, Route route) throws IOException {
......
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
......
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
.......
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
......
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
......
return userResponse.request();
case HTTP_UNAVAILABLE:
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_UNAVAILABLE
return null;
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request();
}
return null;
default:
return null;
}
}
複製代碼
主要就是經過 response code 進行的一系列異常行爲的判斷,從而決定是否須要從新運行起 Interceptor Chain 從面達到重連或者修復網絡的問題。
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// 處理 body,若是有的話
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
// 處理 host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; // 請求以 GZip 來壓縮 if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } // 處理 Cookies,若是有的話 List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } // 處理 UA if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } // 調 proceed() 等待 Response 返回 Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { GzipSource responseBody = new GzipSource(networkResponse.body().source()); Headers strippedHeaders = networkResponse.headers().newBuilder() .removeAll("Content-Encoding") .removeAll("Content-Length") .build(); responseBuilder.headers(strippedHeaders); String contentType = networkResponse.header("Content-Type"); responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody))); } return responseBuilder.build(); } 複製代碼
一樣先用流程圖來總結一下。
BridgeInterceptor 如其名字,橋接器,用於橋接 request 和 reponse。主要就是依據 Http 協議配置請求頭,而後經過 chain.proceed() 發出請求。待結果返回後再構建響應結果。而關於上面代碼各部分的細節,請參考 Http 協議相關文檔以瞭解每一個 Header 的意義。
@Override public Response intercept(Chain chain) throws IOException {
// 從 Cache 中取得 Reponse
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 經過CacheStrategy.Factory構造出CacheStrategy,從而判斷 Cache 是否可用
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
.....
// 命中 Cache
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());
}
}
......
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
// 存儲 Cache
return cacheWritingResponse(cacheRequest, response);
}
}
return response;
}
複製代碼
流程圖總結以下:
上面的實現就是先看一看是否命中 cahce,若是命中的話就直接返回,若是沒有的話就進一步請求網絡。而若是最終有獲取的結果,只要條件容許 ,就會將 response 寫入到 Cache 中。而是否命中 Cache 的實現都在 CacheStrategy 中。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼
其流程圖以下
其主要目的尋找一個可用的 Connection,並將獲取到可用的 httpCodec 以及 connection 推到下一個 Chain 中,以向服務器發起真正的請求。這裏的 Connection 具體點其實指的就是 TCP,甚至包括了 TLS 在內的鏈接。並同時經過 Okio 包裝了寫入流 BufferSink 與讀取流 BufferSource。
關於 Okio,能夠去參考《Okio深刻分析——基礎使用部分》《Okio深刻分析—源碼分析部分》
@Override public Response intercept(Chain chain) throws IOException {
......
// 寫入請求頭
httpCodec.writeRequestHeaders(request);
......
HttpSink httpSink = null;
Response.Builder responseBuilder = null;
......
......
// 若是須要寫入 request body 的話,則寫入
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
.......
......
// 完成請求的寫入
httpCodec.finishRequest();
.......
// 獲取並構造 response
responseBuilder = httpCodec.readResponseHeaders(false);
......
responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis());
Internal.instance.httpSink(responseBuilder, httpSink);
Response response = responseBuilder.build();
.......
.......
return response;
}
複製代碼
這是整個 Interceptor Chain 中最後一個 Intercepter,它利用在 ConnectInterceptor 創建起的鏈接以及獲取到的寫入流,將請求寫入到流中,從而實現向服務發送請求調用。
關於這 5 個 Interceptor 都只是點到即止,並無進行進一步的詳細展開。緣由是由於這 5 個 Interceptor 剛好是這個庫的精華所在,它們其實分別表明了 OkHttp 的基本特性:失敗重連,協議支持,緩存,鏈接重用。每個特性都該獨立成文,同時,從篇幅上來考慮,這裏只作大綱性的展開以及結論性的總結。而且,對這些細節不感興趣的同窗看到這裏也基本瞭解了 OkHttp 的總體輪廓了。
又到了總結的時候了。這篇文章先後也經歷不短的時間,能夠說是跨年了。文章先梳理出了 OkHttp 的代碼結構,以對庫有一個全局的認知。而後以官方 Demo 代碼 AsynchronousGet 的請求流程爲關鍵路徑來分析請求的整個過程,從而從源碼層面瞭解 OkHttp 的工做原理。
最後,感謝你能讀到並讀完此文章。受限於做者水平有限,若是分析的過程當中存在錯誤或者疑問都歡迎留言討論。若是個人分享可以幫助到你,也請記得幫忙點個贊吧,鼓勵我繼續寫下去,謝謝。