Android中okhttp原理詳解

目錄

1、okhttp工作的大致流程
2、okhttp中的連接
3、Dispatcher和線程池
4、OkHttp中的設(shè)計模式
5、OkHttp的優(yōu)勢
6、參考連接

1、okhttp工作的大致流程

1.1、整體流程

(1)、當我們通過OkhttpClient創(chuàng)建一個Call,并發(fā)起同步或異步請求時;
(2)、okhttp會通過Dispatcher對我們所有的RealCall(Call的具體實現(xiàn)類)進行統(tǒng)一管理,并通過execute()及enqueue()方法對同步或異步請求進行處理;
(3)、execute()及enqueue()這兩個方法會最終調(diào)用RealCall中的getResponseWithInterceptorChain()方法,從攔截器鏈中獲取返回結(jié)果;
(4)、攔截器鏈中,依次通過RetryAndFollowUpInterceptor(重定向攔截器)、BridgeInterceptor(橋接攔截器)、CacheInterceptor(緩存攔截器)、ConnectInterceptor(連接攔截器)、CallServerInterceptor(網(wǎng)絡(luò)攔截器)對請求依次處理,與服務(wù)的建立連接后,獲取返回數(shù)據(jù),再經(jīng)過上述攔截器依次處理后,最后將結(jié)果返回給調(diào)用方。
提供兩張圖便于理解和記憶:


okhttp整體流程1
okhttp整體流程2

這張圖只畫出了請求流程,沒有數(shù)據(jù)返回流程,后期會處理。

1.2、各大攔截器的原理解析

1.2.1、RetryAndFollowUpInterceptor:負責重定向

構(gòu)建一個StreamAllocation對象,然后調(diào)用下一個攔截器獲取結(jié)果,從返回結(jié)果中獲取重定向的request,如果重定向的request不為空的話,并且不超過重定向最大次數(shù)的話就進行重定向,否則返回結(jié)果。注意:這里是通過一個while(true)的循環(huán)完成下一輪的重定向請求。

(1)、StreamAllocation為什么在第一個攔截器中就進行創(chuàng)建?
???????便于取消請求以及出錯釋放資源。
(2)、StreamAllocation的作用是什么?
???????StreamAllocation負責統(tǒng)籌管理Connection、Stream、Call三個實體類,具體就是為一個Call(Realcall),尋找( findConnection() )一個Connection(RealConnection),獲取一個Stream(HttpCode)。

1.2.2、BridgeInterceptor

負責將原始Requset轉(zhuǎn)換給發(fā)送給服務(wù)端的Request以及將Response轉(zhuǎn)化成對調(diào)用方友好的Response。
具體就是對request添加Content-Type、Content-Length、cookie、Connection、Host、Accept-Encoding等請求頭以及對返回結(jié)果進行解壓、保持cookie等。

1.2.3、CacheInterceptor

CacheInterceptor:負責讀取緩存以及更新緩存。
在請求階段:

  1. 讀取候選緩存cacheCandidate;
  2. 根據(jù)originOequest和cacheresponse創(chuàng)建緩存策略CacheStrategy;
  3. 根據(jù)緩存策略,來決定是否使用網(wǎng)絡(luò)或者使用緩存或者返回錯誤。
    具體的的緩存策略就是http的緩存策略,詳見下圖:
    在結(jié)果返回階段:
    負責將網(wǎng)絡(luò)結(jié)果進行緩存(使用于DiskLruCache)。
okhttp&http緩存策略

強制緩存:當客戶端第一次請求數(shù)據(jù)是,服務(wù)端返回了緩存的過期時間(Expires與Cache-Control),沒有過期就可以繼續(xù)使用緩存,否則則不適用,無需再向服務(wù)端詢問。
對比緩存:當客戶端第一次請求數(shù)據(jù)時,服務(wù)端會將緩存標識(Etag/If-None-Match與Last-Modified/If-Modified-Since)與數(shù)據(jù)一起返回給客戶端,客戶端將兩者都備份到緩存中 ,再次請求數(shù)據(jù)時,客戶端將上次備份的緩存
標識發(fā)送給服務(wù)端,服務(wù)端根據(jù)緩存標識進行判斷,如果返回304,則表示緩存可用,如果返回200,標識緩存不可用,使用最新返回的數(shù)據(jù)。

ETag是用資源標識碼標識資源是否被修改,Last-Modified是用時間戳標識資源是否被修改。ETag優(yōu)先級高于Last-Modified。

1.2.4、ConnectInterceptor:負責與服務(wù)器建立連接

使用StreamAllocation.newStream來和服務(wù)端建立連接,并返回輸入輸出流(HttpCodec),實際上是通過StreamAllocation中的findConnection尋找一個可用的Connection,然后調(diào)用Connection的connect方法,使用socket與服務(wù)端建立連接。

1.2.5、CallServerInterceptor:負責從服務(wù)器讀取響應(yīng)的數(shù)據(jù)

主要的工作就是把請求的Request寫入到服務(wù)端,然后從服務(wù)端讀取Response。
(1)、寫入請求頭
(2)、寫入請求體
(3)、讀取響應(yīng)頭
(4)、讀取響應(yīng)體

2、連接池原理

由于HTTP是基于TCP,TCP連接時需要經(jīng)過三次握手,為了加快網(wǎng)絡(luò)訪問速度,我們可以Reuqst的header中將Connection設(shè)置為keepalive來復(fù)用連接。

Okhttp支持5個并發(fā)KeepAlive,默認鏈路生命為5分鐘(鏈路空閑后,保持存活的時間),連接池有ConectionPool實現(xiàn),對連接進行回收和管理。

2.1、連接池的清理

連接池清理1

ConectionPool在內(nèi)部使用一個異步線程來清理連接。
當連接池中有連接時:清理任務(wù)由cleanup()方法完成,首先執(zhí)行清理,并返回下次需要清理的間隔時間,調(diào)用調(diào)用wait() 方法釋放鎖。等時間到了以后,再次進行清理,并返回下一次需要清理的時間間隔,再次進入wait,以此循環(huán)往復(fù)。
當連接池中沒有連接時:cleanup()返回-1,跳出循環(huán),下次有連接加進來時,再次開啟線程進行循環(huán)清理。

之所以連接池線程可以跳出循環(huán),是因為,他是子線程,
而looper選擇一直阻塞是因為他是主線程,如果跳出,程序執(zhí)行結(jié)束。

連接池原理2

1、首先統(tǒng)計空閑連接數(shù)量;
2、然后通過for循環(huán)查找最長空閑時間的連接以及對應(yīng)空閑時長;
3、然后判斷這個最長空閑時間的連接是否超出最大空閑連接數(shù)或者或者超過最大空閑時間,滿足其一則清除最長空閑的連接。如果不滿足清理條件,則返回一個對應(yīng)等待時間。
這個對應(yīng)等待的時間又分二種情況:
1 有空閑連接:則返回:keepAliveDurationNs-longestIdleDurationNs;
2 沒有空閑的連接,則返回:keepAliveDurationNs
注意:清除一個空閑連接后,會返回0,再次立即開始清理。

如何統(tǒng)計空閑連接呢?


統(tǒng)計空閑連接

StreamAllocation創(chuàng)建或者復(fù)用一個Connection后,會將自己添加到Connection的connection.allocations列表中,數(shù)據(jù)讀取完畢之后,會將自己從Connection的connection.allocations中移除,所以判讀一個Connection是否是空閑連接可以采用引用計數(shù)法,判斷connection.allocations列表中是否有StreamAllocation,如果沒有就是空閑連接,否則不是。

3、OkHttp中Dispatcher和線程池

3.1、OkHttp中線程池

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;
  }

OkHttp中的線程池是一個 newCachedThreadPool。
所以在 OkHttp 中線程池只是一個輔助作用,僅僅是用來做線程緩存,便于復(fù)用的。
真正控制請求并發(fā)數(shù)量和執(zhí)行時機是通過調(diào)度器 Dispatcher 完成的。

3.2、OkHttp中Dispatcher

RealCall.execute
RealCall.execute

先將RealCall加入Dispatcher的runningSyncCalls隊列,然后調(diào)用getResponseWithInterceptorChain獲取Response,最后調(diào)用Dispatcher的finished方法,將自身從runningSyncCalls移除,然后進行輪詢readyAsyncCalls隊列,取出ready的異步任務(wù)在滿足條件的情況下進行執(zhí)行。

RealCall.enqueue
RealCall.enqueue

如果當前正在執(zhí)行的RealCall的數(shù)量小于最大并發(fā)數(shù)maxRequest(64),并且該call對應(yīng)的Host上的call小于同一host上的最大并發(fā)數(shù)maxRequestsPerHos(5),則將該call加入runningAsyncCalls,并將這個call放到線程池中進行執(zhí)行,否則加入readyAsyncCall排隊等待。

注意:

同步請求和異步請求執(zhí)行完成之后,都會調(diào)用dispatcher的finished方法,將自身從對應(yīng)的隊列中移除,然后進行輪詢readyAsyncCalls隊列,取出ready的異步任務(wù)在滿足條件下放到線程池中執(zhí)行。


輪詢readyAsyncCalls
Dispatcher.中的并發(fā)數(shù)量及三個隊列的作用

maxRequests = 64 // 最大并發(fā)請求數(shù)為64
maxRequestsPerHost = 5 //每個主機最大請求數(shù)為5
ExecutorService executorService //消費者池(也就是線程池)
Deque<AsyncCall> readyAsyncCalls: // 異步的緩存,正在準備被消費的(用數(shù)組實現(xiàn),可自動擴容,無大小限制)
Deque<AsyncCall> runningAsyncCalls //正在運行的 異步的任務(wù)集合,僅僅是用來引用正在運行的任務(wù)以判斷并發(fā)量,注意它并不是消費者緩存
Deque<RealCall> runningSyncCalls //正在運行的,同步的任務(wù)集合。僅僅是用來引用正在運行的同步任務(wù)以判斷并發(fā)量

4、OkHttp中的設(shè)計模式

責任鏈模式:攔截器鏈
單例模式:線程池
觀察者模式:各種回調(diào)監(jiān)聽
策略模式:緩存策略
Builder模式:OkHttpClient的構(gòu)建過程
外觀模式:OkHttpClient封裝了很對類對象
工廠模式:Socket的生產(chǎn)

5、OkHttp的優(yōu)勢

5.1、功能方面:

功能全面,滿足了網(wǎng)絡(luò)請求的大部分需求。

5.2、網(wǎng)絡(luò)優(yōu)化方面:

(1)內(nèi)置連接池,支持連接復(fù)用
(2)支持gzip壓縮響應(yīng)體
(3)通過緩存避免重復(fù)的請求
(4)支持http2,對一臺機器的所有請求共享同一個socket

5.3、擴展性方面:

攔截器模式使得我們很容易使得我們很容易添加一個自定義攔截器對請求和返回結(jié)果進行處理。

6、參考鏈接

6.1、http://www.itdecent.cn/p/6166d28983a2
6.2、https://juejin.im/post/5a704ed05188255a8817f4c9#heading-15

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Okhttp 基礎(chǔ)知識導圖 Okhttp 使用1,創(chuàng)建一個客戶端。2,創(chuàng)建一個請求。3,發(fā)起請求(入?yún)⒒卣{(diào))。 一...
    gczxbb閱讀 2,153評論 0 2
  • OkHttp解析系列 OkHttp解析(一)從用法看清原理OkHttp解析(二)網(wǎng)絡(luò)連接OkHttp解析(三)關(guān)于...
    Hohohong閱讀 21,122評論 4 58
  • 前言 用OkHttp很久了,也看了很多人寫的源碼分析,在這里結(jié)合自己的感悟,記錄一下對OkHttp源碼理解的幾點心...
    Java小鋪閱讀 1,607評論 0 13
  • 文╱楊曉輝 窗外雪綿綿,放眼量觀。河水冰封天地寒。 又是一年歲末時,雪兆來年。 新春佳節(jié)至,北國江南,萬眾歡騰過大...
    吾心向佛閱讀 371評論 0 2
  • 這一年,小月六十五歲。在小月剛剛過完生日的第二天,建鄴安安靜靜的走了,如果忽略他瘦成二指寬的臉,你會覺得他似乎沒有...
    木有鬼閱讀 1,859評論 1 4

友情鏈接更多精彩內(nèi)容