OKHttp概覽

1,總體思路

從使用方法出發,首先是怎麼使用,其次是咱們使用的功能在內部是如何實現的,實現方案上有什麼技巧,有什麼範式。全文基本上是對 OkHttp 源碼的一個分析與導讀,很是建議你們下載 OkHttp 源碼以後,跟着本文,過一遍源碼。對於技巧和範式,因爲目前個人功力還不到位,分析內容沒多少,歡迎你們和我一塊兒討論。java

首先放一張完整流程圖(看不懂不要緊,慢慢日後看):android

okhttp_full_process

2,基本用例

來自 OkHttp 官方網站git

2.1,建立 OkHttpClient 對象

 

咦,怎麼不見 builder?莫急,且看其構造函數:github

 

原來是方便咱們使用,提供了一個「快捷操做」,所有使用了默認的配置。OkHttpClient.Builder類成員不少,後面咱們再慢慢分析,這裏先暫時略過:算法

 

2.2,發起 HTTP 請求

 

OkHttpClient 實現了 Call.Factory,負責根據請求建立新的 Call,在 拆輪子系列:拆 Retrofit中咱們曾和它發生過一次短暫的遭遇:編程

callFactory 負責建立 HTTP 請求,HTTP 請求被抽象爲了 okhttp3.Call 類,它表示一個已經準備好,能夠隨時執行的 HTTP 請求c#

那咱們如今就來看看它是如何建立 Call 的:設計模式

 

如此看來功勞全在 RealCall 類了,下面咱們一邊分析同步網絡請求的過程,一邊瞭解 RealCall的具體內容。緩存

2.2.1,同步網絡請求

咱們首先看 RealCall#execute服務器

 

這裏咱們作了 4 件事:

  1. 檢查這個 call 是否已經被執行了,每一個 call 只能被執行一次,若是想要一個徹底同樣的 call,能夠利用 call#clone 方法進行克隆。
  2. 利用 client.dispatcher().executed(this) 來進行實際執行,dispatcher 是剛纔看到的 OkHttpClient.Builder 的成員之一,它的文檔說本身是異步 HTTP 請求的執行策略,如今看來,同步請求它也有摻和。
  3. 調用 getResponseWithInterceptorChain() 函數獲取 HTTP 返回結果,從函數名能夠看出,這一步還會進行一系列「攔截」操做。
  4. 最後還要通知 dispatcher 本身已經執行完畢。

dispatcher 這裏咱們不過分關注,在同步執行的流程中,涉及到 dispatcher 的內容只不過是告知它咱們的執行狀態,好比開始執行了(調用 executed),好比執行完畢了(調用 finished),在異步執行流程中它會有更多的參與。

真正發出網絡請求,解析返回結果的,仍是 getResponseWithInterceptorChain

 

在 OkHttp 開發者之一介紹 OkHttp 的文章裏面,做者講到:

the whole thing is just a stack of built-in interceptors.

可見 Interceptor 是 OkHttp 最核心的一個東西,不要誤覺得它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網絡請求、緩存、透明壓縮等功能都統一了起來,每個功能都只是一個 Interceptor,它們再鏈接成一個 Interceptor.Chain,環環相扣,最終圓滿完成一次網絡請求。

從 getResponseWithInterceptorChain 函數咱們能夠看到,Interceptor.Chain 的分佈依次是:

okhttp_interceptors

  1. 在配置 OkHttpClient 時設置的 interceptors
  2. 負責失敗重試以及重定向的 RetryAndFollowUpInterceptor
  3. 負責把用戶構造的請求轉換爲發送到服務器的請求、把服務器返回的響應轉換爲用戶友好的響應的 BridgeInterceptor
  4. 負責讀取緩存直接返回、更新緩存的 CacheInterceptor
  5. 負責和服務器創建鏈接的 ConnectInterceptor
  6. 配置 OkHttpClient 時設置的 networkInterceptors
  7. 負責向服務器發送請求數據、從服務器讀取響應數據的 CallServerInterceptor

在這裏,位置決定了功能,最後一個 Interceptor 必定是負責和服務器實際通信的,重定向、緩存等必定是在實際通信以前的。

責任鏈模式在這個 Interceptor 鏈條中獲得了很好的實踐(感謝 Stay 一語道破,自愧弗如)。

它包含了一些命令對象和一系列的處理對象,每個處理對象決定它能處理哪些命令對象,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法。

對於把 Request 變成 Response 這件事來講,每一個 Interceptor 均可能完成這件事,因此咱們循着鏈條讓每一個 Interceptor 自行決定可否完成任務以及怎麼完成任務(自力更生或者交給下一個 Interceptor)。這樣一來,完成網絡請求這件事就完全從 RealCall 類中剝離了出來,簡化了各自的責任和邏輯。兩個字:優雅!

責任鏈模式在安卓系統中也有比較典型的實踐,例如 view 系統對點擊事件(TouchEvent)的處理,具體能夠參考Android設計模式源碼解析之責任鏈模式中相關的分析。

回到 OkHttp,在這裏咱們先簡單分析一下 ConnectInterceptor 和 CallServerInterceptor,看看 OkHttp 是怎麼進行和服務器的實際通訊的。

2.2.1.1,創建鏈接:ConnectInterceptor
 

實際上創建鏈接就是建立了一個 HttpCodec 對象,它將在後面的步驟中被使用,那它又是何方神聖呢?它是對 HTTP 協議操做的抽象,有兩個實現:Http1Codec 和 Http2Codec,顧名思義,它們分別對應 HTTP/1.1 和 HTTP/2 版本的實現。

在 Http1Codec 中,它利用 Okio 對 Socket 的讀寫操做進行封裝,Okio 之後有機會再進行分析,如今讓咱們對它們保持一個簡單地認識:它對 java.io 和 java.nio 進行了封裝,讓咱們更便捷高效的進行 IO 操做。

而建立 HttpCodec 對象的過程涉及到 StreamAllocationRealConnection,代碼較長,這裏就不展開,這個過程歸納來講,就是找到一個可用的 RealConnection,再利用 RealConnection的輸入輸出(BufferedSource 和 BufferedSink)建立 HttpCodec 對象,供後續步驟使用。

2.2.1.2,發送和接收數據:CallServerInterceptor
 

咱們抓住主幹部分:

  1. 向服務器發送 request header;
  2. 若是有 request body,就向服務器發送;
  3. 讀取 response header,先構造一個 Response 對象;
  4. 若是有 response body,就在 3 的基礎上加上 body 構造一個新的 Response 對象;

這裏咱們能夠看到,核心工做都由 HttpCodec 對象完成,而 HttpCodec 實際上利用的是 Okio,而 Okio 實際上仍是用的 Socket,因此沒什麼神祕的,只不過一層套一層,層數有點多。

其實 Interceptor 的設計也是一種分層的思想,每一個 Interceptor 就是一層。爲何要套這麼多層呢?分層的思想在 TCP/IP 協議中就體現得淋漓盡致,分層簡化了每一層的邏輯,每層只須要關注本身的責任(單一原則思想也在此體現),而各層之間經過約定的接口/協議進行合做(面向接口編程思想),共同完成複雜的任務。

簡單應該是咱們的終極追求之一,儘管有時爲了達成目標不得不復雜,但若是有另外一種更簡單的方式,我想應該沒有人不肯意替換。

2.2.2,發起異步網絡請求

 

這裏咱們就能看到 dispatcher 在異步執行時發揮的做用了,若是當前還能執行一個併發請求,那就當即執行,不然加入 readyAsyncCalls 隊列,而正在執行的請求執行完畢以後,會調用 promoteCalls() 函數,來把 readyAsyncCalls 隊列中的 AsyncCall 「提高」爲 runningAsyncCalls,並開始執行。

這裏的 AsyncCall 是 RealCall 的一個內部類,它實現了 Runnable,因此能夠被提交到 ExecutorService 上執行,而它在執行時會調用 getResponseWithInterceptorChain() 函數,並把結果經過 responseCallback 傳遞給上層使用者。

這樣看來,同步請求和異步請求的原理是同樣的,都是在 getResponseWithInterceptorChain()函數中經過 Interceptor 鏈條來實現的網絡請求邏輯,而異步則是經過 ExecutorService 實現。

2.3,返回數據的獲取

在上述同步(Call#execute() 執行以後)或者異步(Callback#onResponse() 回調中)請求完成以後,咱們就能夠從 Response 對象中獲取到響應數據了,包括 HTTP status code,status message,response header,response body 等。這裏 body 部分最爲特殊,由於服務器返回的數據可能很是大,因此必須經過數據流的方式來進行訪問(固然也提供了諸如 string() 和 bytes() 這樣的方法將流內的數據一次性讀取完畢),而響應中其餘部分則能夠隨意獲取。

響應 body 被封裝到 ResponseBody 類中,該類主要有兩點須要注意:

  1. 每一個 body 只能被消費一次,屢次消費會拋出異常;
  2. body 必須被關閉,不然會發生資源泄漏;

在 2.2.1.2,發送和接收數據:CallServerInterceptor 小節中,咱們就看過了 body 相關的代碼:

 

由 HttpCodec#openResponseBody 提供具體 HTTP 協議版本的響應 body,而 HttpCodec 則是利用 Okio 實現具體的數據 IO 操做。

這裏有一點值得一提,OkHttp 對響應的校驗很是嚴格,HTTP status line 不能有任何雜亂的數據,不然就會拋出異常,在咱們公司項目的實踐中,因爲服務器的問題,偶爾 status line 會有額外數據,而服務端的問題也毫無頭緒,致使咱們不得不忍痛繼續使用 HttpUrlConnection,然後者在一些系統上又存在各類其餘的問題,例如魅族系統發送 multi-part form 的時候就會出現沒有響應的問題。

2.4,HTTP 緩存

在 2.2.1,同步網絡請求 小節中,咱們已經看到了 Interceptor 的佈局,在創建鏈接、和服務器通信以前,就是 CacheInterceptor,在創建鏈接以前,咱們檢查響應是否已經被緩存、緩存是否可用,若是是則直接返回緩存的數據,不然就進行後面的流程,並在返回以前,把網絡的數據寫入緩存。

這塊代碼比較多,但也很直觀,主要涉及 HTTP 協議緩存細節的實現,而具體的緩存邏輯 OkHttp 內置封裝了一個 Cache 類,它利用 DiskLruCache,用磁盤上的有限大小空間進行緩存,按照 LRU 算法進行緩存淘汰,這裏也再也不展開。

咱們能夠在構造 OkHttpClient 時設置 Cache 對象,在其構造函數中咱們能夠指定目錄和緩存大小:

 

而若是咱們對 OkHttp 內置的 Cache 類不滿意,咱們能夠自行實現 InternalCache 接口,在構造 OkHttpClient 時進行設置,這樣就可使用咱們自定義的緩存策略了。

3,總結

OkHttp 還有不少細節部分沒有在本文展開,例如 HTTP2/HTTPS 的支持等,但創建一個清晰的概覽很是重要。對總體有了清晰認識以後,細節部分若有須要,再單獨深刻將更加容易。

在文章最後咱們再來回顧一下完整的流程圖:

okhttp_full_process

  • OkHttpClient 實現 Call.Factory,負責爲 Request 建立 Call
  • RealCall 爲具體的 Call 實現,其 enqueue() 異步接口經過 Dispatcher 利用 ExecutorService 實現,而最終進行網絡請求時和同步 execute() 接口一致,都是經過 getResponseWithInterceptorChain() 函數實現;
  • getResponseWithInterceptorChain() 中利用 Interceptor 鏈條,分層實現緩存、透明壓縮、網絡 IO 等功能;
相關文章
相關標籤/搜索