OkHttp 源碼分析

流程分析

咱們從一個簡單的 HTTP 請求開始:java

client = new OkHttpClient();
Request request = new Request.Builder().url("your url").build();
//同步發起請求
Response syncResponse = client.newCall(request).execute();
//異步發起請求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) { }
    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { }
});
複製代碼

上面的代碼將會發起兩個簡單的 HTTP 請求,請求流程以下圖所示。緩存

上面的流程圖只畫到了責任鏈的部分,前面的介紹完後會單獨介紹責任鏈及每一個 interceptor 的原理。服務器

OkHttpClient

咱們使用 new OkHttpClient() 建立一個默認的 OkHttpClient,一樣也可使用 OkHttpClient.Builder 來經過自定義的參數來構造一個 client。 後面咱們使用網絡請求時都將經過這個 client 來進行,能夠將它理解爲整個 OkHttp 的核心類,其對總體 OkHttp 進行了封裝,對外提供了請求的發起以及一些參數配置的接口,咱們能夠經過 OkHttpClient.Builder 來設置,對內負責協調各個類的運做,自己其實並無包含過多的代碼。cookie

Request

Request 很好理解,負責組裝請求。網絡

Call(RealCall)

而後調用 client.newCall(request) 方法,該方法是指建立一個新的將要被執行的請求,經過 newCall 方法獲取一個 Call 對象(實現爲 RealCall),這時咱們使用 Call 的 execute/enqueue 將發起一個同步/異步請求。框架

因此每個 Request 最終將會被封裝成一個 RealCall 對象,RealCall 與 Request 是一一對應的關係,Call 用來描述一個可被執行、中斷的請求,咱們每發起一個請求時就會建立一個 RealCall 對象,最終調用 RealCall#getResponseWithInterceptorChain() 發起請求,該方法將返回一個響應結果 Response。異步

Dispatcher

Dispatcher 用於管理其對應 OkHttpClient 的全部請求,經過上面的流程圖能夠看到,使用異步請求時會將請求委託給 Dispatcer 對象來處理,Dispatcher 對象隨 OkHttpClient 建立而建立編輯器

實際上,Dispatcher 不只用於管理異步請求,也負責管理同步請求,當咱們發起一個請求時,不管是異步仍是同步都會被 Dispatcher 記錄下來。咱們能夠經過 OkHtpClient#dispatcher() 獲取 Dispatcher 對象對請求進行統一的控制,例如結束全部請求、獲取線程池等等。 Dispatcher 中包含三個隊列:ide

  • readyAsyncCalls:一個新的異步請求首先會被加入該隊列中
  • runningAsyncCalls:當前正在運行中的異步請求
  • runningSyncCalls:當前正在運行的同步請求

Dispatcher 中包含一個默認的線程池用於執行全部的異步請求,也能夠經過構造器指定一個線程池,全部的異步請求都將會經過這個線程池來執行。異步請求與同步請求同樣,最終也會調用 RealCall#getResponseWithInterceptorChain() 發起請求,只不過一個是直接調用,一個是在線程池中調用。源碼分析

經過上面的介紹已經發現了關鍵之處就在於那個名字超長的方法,只要調用了它就能返回一個 Response,這個方法就開始涉及到廣爲人知的 OkHttp 的責任鏈模式了。

OkHttp 責任鏈

講真的,網上如今隨便搜搜 OkHttp 源碼的都是在將責任鏈,搞的我都不想講了,可是做爲一個 OkHttp 源碼分析的文章不講又感受過不去,那仍是說一下吧(這裏點名批評一些爲知筆記的 MarkDown 編輯器,寫了一下午的東西說沒就沒,絲毫沒有給我反應的餘地)。

這一切都還要從那個名字超長的方法開始提及,咱們知道,不管如何都會調用 RealCall#getResponseWithInterceptorChain() 發起請求並獲取最終的 Response。

這個方法會根據用戶設置的 Interceptor 以及默認的幾個 Interceptor 組裝 Interceptor 列表,而後建立責任鏈。責任鏈建立好後會調用其 process 方法獲取 Response 並返回,其中涉及兩個概念:Interceptor、Chain

Interceptor

Interceptor 接口做爲一個攔截器的抽象概念,被設計爲責任鏈上的單位節點,用於觀察、攔截、處理請求等,例如添加 Header、重定向、數據處理等等。 Interceptor 之間互相獨立,每一個 Interceptor 只負責本身關注的任務,不與其餘 Interceptor 接觸。 Interceptor 接口中只包含一個方法(OkHttp 如今已經用 Kotlin 重寫了):

interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response
}
複製代碼

intercept 方法接收一個 Chain 做爲參數,並返回一個 Response,該方法的通常處理邏輯以下:

上面的流程中的步驟並非必選的,也不是必定要按照這個步驟來,你徹底能夠按照本身的想法進行你喜歡的各類騷操做。 在 RealCall 中會按照順序添加以下幾個默認的 Interceptor 到責任鏈中用來完成基本功能:

  • 用戶設置的 Interceptor
  • RetryAndFollowUpInterceptor:失敗重試及重定向
  • BridgeInterceptor:處理網絡 Header、Cookie、gzip 等
  • CacheInterceptor:管理緩存
  • ConnectInterceptor:鏈接服務器
  • 若是是 WebSocket 請求則添加對應的 Interceptors
  • CallServerInterceptor:數據發送/接收

後面再詳細介紹這幾個 Interceptor 的具體含義及原理。 責任鏈將會按照添加的順序依次執行這些 Interceptor,因此,順序是很重要的,經過這些 Interceptor 的處理,最終會返回一個完美的 Response 給到 RealCall 裏面的那個名字超長的方法,而後在返回到下游用戶。至此,一個完整的請求就落下帷幕了。

Chain

Chain 被用來描述責任鏈,經過其中的 process 方法開始依次執行鏈上的每一個節點,並返回處理後的 Response。 Chain 的惟一實現爲 RealInterceptorChain(下文簡稱 RIC),RIC 能夠稱之爲攔截器責任鏈,其中的節點由 RealCall 中添加進來的 Interceptor 們組成。因爲 Interceptor 的互相獨立性,RIC 中還會包含一些公共參數及共享的對象。

Interceptor 與 Chain 彼此互相依賴,互相調用,共同發展,造成了一個完美的調用鏈,下面來看下他們的調用關係圖:

經過上圖能夠明確的看到,當咱們在某個 Interceptor 中調用 Chain#process 方法獲取 Response 時,將會依調用當前位置以後的 Interceptor 來處理這個請求,處理完成後把 Response 返回到當前 Interceptor,而後處理完再向上級返回,直到遍歷結束。

網絡鏈接與數據收發

上面已經介紹了 OkHttp 的基本概念、基礎配置、線程控制、責任鏈,下面再說說一個網絡框架的靈魂:網絡請求的創建與數據收發。 RealCall 中添加的幾個不一樣的 Interceptor 就互相協做完成了這些功能,只要明白了這幾個基礎的 interceptor 就明白了 OkHttp 的靈魂。 其實我不太建議閱讀源碼時太過關心實現細節,只要明白設計思路,大致上的實現就差很少了,否則容易被負責的細節繞暈。 那麼在介紹這幾個 interceptor 以前先介紹一些 OkHttp 中的基本概念。

鏈接如何創建

咱們以前看的 Volley 啊等等不少網絡請求框架不少底層都是經過 HTTPURLConnection 來與服務端創建鏈接的,而 OkHttp 就比較優秀了。由於 HTTP 協議是創建在 TCP/IP 協議基礎之上的,底層仍是走的 Socket,因此 OkHttp 直接使用 Socket 來完成 HTTP 請求。

Route

route 爲用於鏈接到服務器的具體路由。其中包含了 IP 地址、端口、代理等參數。 因爲存在代理或者 DNS 可能返回多個 IP 地址的狀況,因此同一個接口地址可能會對應多個 route。 在建立 Connection 時將會使用 Route 而不是直接用 IP 地址。

RouteSelector

Route 選擇器,其中存儲了全部可用的 route,在準備鏈接時時會經過 RouteSelector#next 方法獲取下一個 Route。 值得注意的是,RouteSelector 中包含了一個 routeDatabase 對象,其中存放着鏈接失敗的 Route,RouteSelector 會將其中存儲的上次鏈接失敗的 route 放在最後,以此提升鏈接速度。

RealConnection

RealConnection 實現了 Connection 接口,其中使用 Socket 創建 HTTP/HTTPS 鏈接,而且獲取 I/O 流,同一個 Connection 可能會承載多個 HTTP 的請求與響應。 其實能夠大概的理解爲是對 Socket 、I/O 流以及一些協議的封裝,這個裏面涉及到的計算機網絡相關的知識較多,例如 TLS 握手,HTTPS 驗證等等。

RealConnectionPool

這是用來存儲 RealConnection 的池子,內部使用一個雙端隊列來進行存儲。 在 OkHttp 中,一個鏈接(RealConnection)用完後不會立馬被關閉並釋放掉,並且是會存儲到鏈接池(RealConnectionPool)中。 除了緩存鏈接外,緩存池還負責按期清理過時的鏈接,在 RealConnection 中會維護一個用來描述該鏈接空閒時間的字段,每添加一個新的鏈接到鏈接池中時都會進行一次檢測,遍歷全部的鏈接,找出當前未被使用且空閒時間最長的那個鏈接,若是該鏈接空閒時長超出閾值,或者鏈接池已滿,將會關閉該鏈接。 另外 RealConnection 中還維護一個 Transmitter 的弱引用列表,用來存儲當前正在使用該鏈接的 Transmitter。當列表爲空時表示該鏈接已經未在使用。

ExchangeCodec

ExchangeCodec 負責對 Request 編碼及解碼 Response,也就是寫入請求及讀取響應,咱們的請求及響應數據都經過它來讀寫。 因此 Connection 負責創建鏈接,ExchangeCodec 負責收發數據。 ExchangeCodec 接口的實現類有兩個:Http1ExchangeCodec 及 Http2ExchangeCodec,分別對應兩種協議版本。

Exchange

Exchange 功能相似 ExchangeCodec,但它是對應的是單個請求,其在 ExchangeCodec 基礎上擔負了一些鏈接管理及事件分發的做用。 具體而言,Exchange 與 Request 一一對應,新建一個請求時就會建立一個 Exchange,該 Exchange 負責將這個請求發送出去並讀取到響應數據,而發送與接收數據使用的是 ExchangeCodec。

Transmitter

Transmitter 是 OkHttp 網絡層的橋樑,咱們上面說的這些概念最終都是經過 Transmitter 來融合在一塊兒,並對外提供功能實現。

好了,如今基本概念介紹完畢,開始看看 interceptor 吧。

RetryAndFollowUpInterceptor

這個 interceptor 顧名思義,負責失敗重試以及重定向。 可能出觸發重試或重定向的條件以下:

  • 401:未受權
  • 407:代理未受權
  • 503:服務未受權
  • 3xx:請求重定向
  • 408:請求超時
  • 以及一些 I/O 異常等等鏈接失敗的狀況

下面看一下其中的邏輯:

咱們上面說過,由於代理及 DNS 的緣由,對於同一個 url 可能會有多個 IP 地址,鏈接時經過 RouteSelector 選擇合適的 Route 進行鏈接,因此這裏的失敗重試並非指對同一 IP 地址的屢次重試,是逐個嘗試路由表中的地址。 上面鏈接失敗以後,進行重試時雖然並無其它操做,但實際上開始鏈接時會自動調用下一個 Route 進行鏈接。

上圖流程中有兩處重點這裏再介紹一下,分別是 followUpRequest 及 recover 方法。 recover 方法用於判斷鏈接是否能夠恢復重試,代碼以下:

private fun recover( e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request ): Boolean {
    // 用戶設置的是否重連參數
    if (!client.retryOnConnectionFailure) return false
    // 單次請求或 FileNotFoundException 異常不可恢復 
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
    // 不可恢復的異常,例如協議錯誤、驗證錯誤等
    if (!isRecoverable(e, requestSendStarted)) return false
    // 沒有更多的路由了
    if (!transmitter.canRetry()) return false
    return true
  }
複製代碼

如今再來看看 followUpRequest 方法,這個代碼較多就不貼了。 當走到這個方法時意味着已經鏈接到服務器並接收到響應了,此時須要經過響應碼來判斷是否須要重定向。

若是響應碼爲 401 或者 407 則表示請求未認證,此時從新對請求進行認證,而後返回認證後的 Request。

響應碼爲 3xx 表示重定向,此時重定向地址在響應 Header 的 Location 字段中,而後經過這個新的地址以及以前的 Request 構建一個新的 Request 並返回。

響應碼 503 表示服務器錯誤,但這個是暫時的,可能立刻就會恢復,因此會直接返回以前的請求。

BridgeInterceptor

BridgeInterceptor 是用戶與網絡之間的橋樑,負責將用戶請求轉換爲網絡請求,也就是根據 Request 信息組建網絡 Header 以及設置響應數據。 實際上,BridgeInterceptor 除了設置例如 Content-Length、Connect、Host 之類的基本請求頭以外,還負責設置 Cookie 以及 gzip. BridgeInterceptor 在開始進行網絡請求以前會先經過 url 判斷是否有 cookie,有的話就會把這個 cookie 帶上,請求結束後一樣也會判斷響應頭是否包含 Set-Cookie 字段,包含則會將其存下來下次使用。可是存儲 Cookie 的操做會委託給 CookieJar 來實現,OkHttp 默認提供了一個空的 CookieJar 對象,也就是說默認不會作出任何操做,但能夠在建立 OkHttp 時指定一個本身的 CookieJar 來使用。

若是 Request 請求頭中沒有包含 Accept-Encoding 以及 Range 字段則會給其添加一個 "Accept-Encoding: gzip" 請求頭,接收到響應數據後若是響應表示使用了 gzip 則會把響應數據交給 okio 的 GzipSource 解碼。

CacheInterceptor

CacheInterceptor 負責緩存響應數據。 該方法首先會經過 Cache 對象嘗試獲取緩存的數據,而後再經過 CacheStrategy 獲取緩存策略,經過該策略的計算結果,咱們能夠獲取到兩個可空對象:networkRequest 以及 cacheResponse。 其中 networkRequest 爲原始 Request 但可能爲空,具體是否是空的經過 CacheStrategy 控制。 cacheResponse 是經過 Cache 獲取到的 Response,同上,一樣也可能爲空。 而後就能夠經過判斷兩個對象的可空性來處理緩存,邏輯以下:

  • 若是二者都爲空,則表示既禁止了使用網絡請求,也不可使用緩存或者未命中緩存,直接返回 504 錯誤。
  • 若是隻有 networkRequest 爲空,表示禁止了網絡請求,此時直接返回從緩存中命中的 Response。
  • 若是二者都不爲空,則開始發起請求並獲取響應數據。
  • 若是此時 cacheResponse 不爲空,且響應碼爲 304,直接返回 cacheResponse,並使用響應數據更新緩存。
  • 若是 cacheResponse 爲空則會將響應數據存儲到 Cache 中。
  • 返回響應數據。

須要注意,上面說的 Cache 對象默認爲空,若是爲空則與其相關的操做都不會被執行,且 cacheResponse 必定爲空。 咱們能夠在 OkHttpClient 中設置 Cache。

ConnectInterceptor

ConnectInterceptor 用來打開一個到服務端的鏈接。 其中代碼很簡單,會經過 Transmitter#newExchange 方法建立一個 Exchange 對象,並調用 Chain#process 方法。

newExchange 方法中會先經過 ExchangeFinder 嘗試去 RealConnectionPool 中尋找已存在的鏈接,未找到則會從新建立一個 RealConnection 並開始鏈接,而後將其存入 RealConnectionPool,此時已經準備好了 RealConnection 對象,而後經過請求協議建立不一樣的 ExchangeCodec 並返回。具體細節上面已經說過了,這裏不作詳細介紹。 經過上面面步驟建立好 ExchangeCodec 以後,再根據它以及其餘參數建立 Exchange 對象並返回。

ConnectInterceptor 將 newExchange 方法返回的 Exchange 對象做爲參數,調用 Chain#process 方法。

CallServerInterceptor

CallServerInterceptor 負責讀寫數據。 這是最後一個 interceptor 了,到了這裏該準備的都準備好了,經過它,將會把 Request 中的數據發送到服務端,並獲取到數據寫入 Response。

上圖爲該 interceptor 總體流程圖。其中操做主要都放在了 Exchange 等對象中,這裏不作過多介紹。

那麼 OkHttp 的源碼到這裏就分析的差很少啦,其實還有不少東西都沒有講到,OkHttp 是個龐大的框架,其中涉及到的東西實在太多了,並且包括了不少計算機網絡的基礎知識,奈何本人才疏學淺,就只講這麼多吧。

若是以爲還不錯的話,歡迎關注個人我的公衆號,我會不按期發一些乾貨文章~

相關文章
相關標籤/搜索