文章思維導圖
前言
進行 okhttp
的核心源碼分析,必須要搞清楚 http 協議以及相關的網絡協議。這里只對協議容易混淆的地方進行說明。
首先我們要明確一點,要想讓兩台計算機進行通信,首先需要建立連接,也就是我們常說的三次握手。
計算機A 要想和計算機 B 進行通信,首先要知道計算機B 的IP 地址,知道 IP 地址后,就能訪問計算機B,而要和計算機B上的那個程序通信,這個時候就需要 TCP 的地址了,也就是端口號。有了這兩個信息,兩者就可以建立連接了。其實這個時候就可以進行通信了。體現在代碼中,就是通過 Socket
建立連接,然后進行讀寫操作。
這個時候也看出來了,所謂的 Socket
其實就是對 TCP/IP 建立連接通信的一種具體封裝,不同的語言代碼有不同的實現。有了 Socket
后,開發者就可以方便地進行網絡連接通信了。Socket 本身並不是 TCP/IP網絡協議中的協議
HTTP
協議就是在建立了 Socket
連接后規定了通信內容的格式,大家都遵從這個協議進行通信。
HTTP通信內容?
簡單來說,所謂的 HTTP 通信就是建立 Socket 連接,然后把通信內容拼接成符合 HTTP 報文的內容發送出去。
什么是 okhttp
有了前言的內容,我們就可以理解什么是 okhttp
了,所謂的 okhttp
就是通過代碼的方式實現了各種協議,將這些通信協議封裝起來,讓我們可以快速地用代碼來實現。
okhttp 好處
-
支持 HTTP1、HTTP2、Quic以及 WebSocket
之所以支持,是因為
okhttp
的源碼里面對這些協議的規則進行了實現。 -
連接池復用底層 TCP連接,減少請求延時。
建立 TCP 連接是需要時間的,okHTTP 源碼中對已經連接的 TCP,其實在代碼中的體現就是
Socket
進行了緩存,再次請求同一地址的時候就不用重復建立連接了,從而減少請求延時。 -
無縫的支持 GZIP 減少數據流量
其實這是 HTTP 協議的內容,HTTP 協議中可以在請求頭中規定是否支持數據壓縮,okhttp 就把這個請求頭封裝進去了,告訴服務器,我可以接受一個 GZIP 壓縮的數據報文。
-
緩存響應數據減少重復的網絡請求
這也是 HTTP 協議中定義的內容,有對應的字段表示
-
請求失敗自動重試主機的其他 ip,自動重定向
同樣也是 HTTP 協議中定義的內容。
可見所謂的這些好處,其實就是 okhttp
利用 HTTP 報文格式中規定的內容,然后進行處理,完成這些規定。如果沒有處理的話,任憑服務器發送來的報文是什么,如果統統不管的話,那也是沒用的。
因此再次說明 HTTP 協議只是規定了你我通信要發送的內容需要遵從什么樣的格式,至於我有沒有根據內容,實現對應的功能,那就不是 HTTP 協議的范圍了。
okhttp 使用
// 步驟 1
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60,TimeUnit.SECONDS)
.connectTimeout(60,TimeUnit.SECONDS)
.cache(new Cache(new File("xx"),1024))
.build();
// 步驟 2
Request request = new Request.Builder()
.url(AppConfig.URL.url_get)
.build();
// 步驟 3
Call call = okHttpClient.newCall(request);
// 步驟 4
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
okhttp
的使用一般就是上面 4 步。
第一步:創建 okhttpClient
,也就是一個客戶端,同時也是也 Call 的工廠,主要作用就是記錄一些配置內容,比如 : 連接超時時間、讀取超時時間、緩存地址等等這種配置。這個對象可以共用,不用每次都創建。
第二步:就是創建一個請求報文。
第三步:就是通過 Call 工廠 okhttpClient
和 請求報文,構建出一個准備好要執行的請求。通過 Call 來發起網絡請求。
第四步:發起網絡請求
雖然就這樣簡單的四步,但是代碼設計的非常好,首先做到了功能分離!單一職責,有三個類 OkhttpClient
、Request
、Call
分別負責不同的職責,非常清晰。
核心原理分析
okhttp 整個大的流程核心就是一個分發器 Dispatcher
和 攔截器 interceptors
下面分別分析
Dispatcher
分發器用於執行我們網絡請求的異步任務,Dispatcher
有 4 個核心成員對象 :
-
ExecutorService
線程池,用於執行異步任務
-
三個隊列
Deque<AsynCall>
三個隊列,分別是用於存儲 正在執行的異步任務、正在執行的同步任務、准備執行的異步任務
先看一張流程圖
Dispatcher
的整個執行流程就如上圖所示,下面來結合源碼分別介紹。
// 異步請求
call.enqueue(new Callback() );
// 這個時候會執行 RealCall 下面的方法
這個時候就進入 Dispatcher
分發器
重點來了
首先這個方法是個同步方法,有個判斷,判斷這個請求是放入 running
隊列還是 ready
隊列。
圖中的1 就是判斷條件,如果 running 隊列中的 call 小於最大請求數(默認 64)並且對同一地址的請求小於 最大主機請求數(默認5),這個時候就放入 running
隊列,直接交給線程池來執行 請求。否則加入 ready
隊列,等待請求。
然后看 executorService().execute(call)
這一步其實就是交給線程池執行,最終執行的是 AsyncCall
的 execute()
方法
注意這個方法是在子線程中執行的。
1: 是真正的觸發網絡請求,進入下一個核心點 “攔截器”。(后面講解)
2:可以看到 2 是在 finally
中執行的,也就是總是會執行到。
1:執行完畢后就把 call 從 runing 隊列中移除了,然后執行 2
這里會根據條件循環判斷 ready
隊列中的 call 是否能添加到 running
隊列中執行。
到此整個分發器的執行流程就結束了!
總結
對於 Dispatcher
分發器核心點就是一個線程池、維持請求隊列。
添加一個請求的時候會判斷正在請求的數量,如果條件滿足就放入線程池執行,否則放入等待隊列,等待執行。
后面我們會繼續介紹下一個核心-----攔截器