OKHTTP

OkHttp

1.Okhttp 基本實(shí)現(xiàn)原理

OkHttp 主要是通過(guò) 5 個(gè)[攔截器]和 3 個(gè)雙端隊(duì)列(2 個(gè)異步隊(duì)列,1 個(gè)同步隊(duì)列)工作。內(nèi)部實(shí)現(xiàn)通過(guò)一個(gè)責(zé)任鏈模式完成,將網(wǎng)絡(luò)請(qǐng)求的各個(gè)階段封裝到各個(gè)鏈條中,實(shí)現(xiàn)了各層的解耦。

# Dispatcher
//異步任務(wù)等待隊(duì)列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
//異步任務(wù)隊(duì)列
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
//同步任務(wù)隊(duì)列
private val runningSyncCalls = ArrayDeque<RealCall>()

OkHttp 的底層是通過(guò) Socket 發(fā)送 HTTP 請(qǐng)求與接受響應(yīng),但是 OkHttp 實(shí)現(xiàn)了連接池的概念,即對(duì)于同一主機(jī)的多個(gè)請(qǐng)求,可以公用一個(gè) Socket 連接,而不是每次發(fā)送完 HTTP 請(qǐng)求就關(guān)閉底層的 Socket,這樣就實(shí)現(xiàn)了連接池的概念。而 OkHttp 對(duì) Socket 的讀寫操作使用的 OkIo 庫(kù)進(jìn)行了一層封裝。

執(zhí)行流程:

  • 通過(guò)構(gòu)建者構(gòu)建出OkHttpClient對(duì)象,再通過(guò)newCall方法獲得RealCall請(qǐng)求對(duì)象.
  • 通過(guò)RealCall發(fā)起同步或異步請(qǐng)求,而決定是異步還是同步請(qǐng)求的是由線程分發(fā)器dispatcher來(lái)決定.
  • 當(dāng)發(fā)起同步請(qǐng)求時(shí)會(huì)將請(qǐng)求加入到同步隊(duì)列中依次執(zhí)行,所以會(huì)阻塞UI線程,需要開啟子線程執(zhí)行.
  • 當(dāng)發(fā)起異步請(qǐng)求時(shí)會(huì)創(chuàng)建一個(gè)線程池,并且判斷請(qǐng)求隊(duì)列是否大于最大請(qǐng)求隊(duì)列64,請(qǐng)求主機(jī)數(shù)是否大于5,如果大于請(qǐng)求添加到異步等待隊(duì)列中,否則添加到異步執(zhí)行隊(duì)列,并執(zhí)行任務(wù).

他實(shí)現(xiàn)了一個(gè)RealInterceptorChain,它持有了按照順序構(gòu)建列表interceptors ,先放入用戶自定義的interceptors,然后依次放入RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor,注意有一個(gè)參數(shù)index=0 。
然后調(diào)用了RealInterceptorChain.proceed()方法:

可以發(fā)現(xiàn),RealInterceptorChain會(huì)先找到interceptors 的第一個(gè)(interceptor ),然后再取interceptors 后面的子集,copy一個(gè)新的RealInterceptorChain(next),調(diào)用interceptor.intercept(next)。
interceptor.intercept(next)內(nèi)部一般又回去調(diào)用 proceed(request: Request),形成了遞歸。但是interceptors 會(huì)逐漸的往后減少。這就是典型的責(zé)任鏈模式,每一級(jí)的interceptor調(diào)用到下一級(jí)的interceptor,可以修改request或Response 。
OKHTTP的ConnectInterceptor 內(nèi)部是用的Socket和服務(wù)端建立鏈接的,Socket是TCP的編程API,遵循HTTP報(bào)文協(xié)議就可以用Socket和服務(wù)端進(jìn)行HTTP通信了。同時(shí)Socket還可以復(fù)用。
普通的80端口HTTP鏈接,用普通的Socket即可,443端口的HTTPS鏈接,就要用val socket: Socket = SSLSocketFactory.getDefault().createSocket("www.baidu.com",443)

image.png
  1. OKHTTP的攔截器分析
image.png

addInterceptor與addNetworkInterceptor的區(qū)別

二者通常的叫法為應(yīng)用攔截器和網(wǎng)絡(luò)攔截器,從整個(gè)責(zé)任鏈路來(lái)看,應(yīng)用攔截器是最先執(zhí)行的攔截器,也就是用戶自己設(shè)置request屬性后的原始請(qǐng)求,而網(wǎng)絡(luò)攔截器位于ConnectInterceptor和CallServerInterceptor之間,此時(shí)網(wǎng)絡(luò)鏈路已經(jīng)準(zhǔn)備好,只等待發(fā)送請(qǐng)求數(shù)據(jù)。

  1. 首先,應(yīng)用攔截器在RetryAndFollowUpInterceptor和CacheInterceptor之前,所以一旦發(fā)生錯(cuò)誤重試或者網(wǎng)絡(luò)重定向,網(wǎng)絡(luò)攔截器可能執(zhí)行多次,因?yàn)橄喈?dāng)于進(jìn)行了二次請(qǐng)求,但是應(yīng)用攔截器永遠(yuǎn)只會(huì)觸發(fā)一次。另外如果在CacheInterceptor中命中了緩存就不需要走網(wǎng)絡(luò)請(qǐng)求了,因此會(huì)存在短路網(wǎng)絡(luò)攔截器的情況。
  2. 其次,如上文提到除了CallServerInterceptor,每個(gè)攔截器都應(yīng)該至少調(diào)用一次realChain.proceed方法。實(shí)際上在應(yīng)用攔截器這層可以多次調(diào)用proceed方法(本地異常重試)或者不調(diào)用proceed方法(中斷),但是網(wǎng)絡(luò)攔截器這層連接已經(jīng)準(zhǔn)備好,可且僅可調(diào)用一次proceed方法。
  3. 最后,從使用場(chǎng)景看,應(yīng)用攔截器因?yàn)橹粫?huì)調(diào)用一次,通常用于統(tǒng)計(jì)客戶端的網(wǎng)絡(luò)請(qǐng)求發(fā)起情況;而網(wǎng)絡(luò)攔截器一次調(diào)用代表了一定會(huì)發(fā)起一次網(wǎng)絡(luò)通信,因此通常可用于統(tǒng)計(jì)網(wǎng)絡(luò)鏈路上傳輸?shù)臄?shù)據(jù)。
  1. 網(wǎng)絡(luò)緩存機(jī)制CacheInterceptor

HTTP緩存原理

在HTTP 1.0時(shí)代,響應(yīng)使用Expires頭標(biāo)識(shí)緩存的有效期,其值是一個(gè)絕對(duì)時(shí)間,比如Expires:Thu,31 Dec 2020 23:59:59 GMT。當(dāng)客戶端再次發(fā)出網(wǎng)絡(luò)請(qǐng)求時(shí)可比較當(dāng)前時(shí)間
和上次響應(yīng)的expires時(shí)間進(jìn)行比較,來(lái)決定是使用緩存還是發(fā)起新的請(qǐng)求。
使用Expires頭最大的問(wèn)題是它依賴客戶端的本地時(shí)間,如果用戶自己修改了本地時(shí)間,就會(huì)導(dǎo)致無(wú)法準(zhǔn)確的判斷緩存是否過(guò)期。
因此,從HTTP 1.1 開始使用Cache-Control頭表示緩存狀態(tài),它的優(yōu)先級(jí)高于Expires,常見的取值為下面的一個(gè)或多個(gè)。

  • private,默認(rèn)值,標(biāo)識(shí)那些私有的業(yè)務(wù)邏輯數(shù)據(jù),比如根據(jù)用戶行為下發(fā)的推薦數(shù)據(jù)。該模式下網(wǎng)絡(luò)鏈路中的代理服務(wù)器等節(jié)點(diǎn)不應(yīng)該緩存這部分?jǐn)?shù)據(jù),因?yàn)闆]有實(shí)際意義。
  • public 與private相反,public用于標(biāo)識(shí)那些通用的業(yè)務(wù)數(shù)據(jù),比如獲取新聞列表,所有人看到的都是同一份數(shù)據(jù),因此客戶端、代理服務(wù)器都可以緩存。
  • no-cache 可進(jìn)行緩存,但在客戶端使用緩存前必須要去服務(wù)端進(jìn)行緩存資源有效性的驗(yàn)證,即下文的對(duì)比緩存部分,我們稍后介紹。
  • max-age 表示緩存時(shí)長(zhǎng)單位為秒,指一個(gè)時(shí)間段,比如一年,通常用于不經(jīng)常變化的靜態(tài)資源。
  • no-store 任何節(jié)點(diǎn)禁止使用緩存。

強(qiáng)制緩存
在上述緩存頭規(guī)約基礎(chǔ)之上,強(qiáng)制緩存是指網(wǎng)絡(luò)請(qǐng)求響應(yīng)header標(biāo)識(shí)了Expires或Cache-Control帶了max-age信息,而此時(shí)客戶端計(jì)算緩存并未過(guò)期,則可以直接使用本地緩存內(nèi)容,而不用真正的發(fā)起一次網(wǎng)絡(luò)請(qǐng)求。
協(xié)商緩存
強(qiáng)制緩存最大的問(wèn)題是,一旦服務(wù)端資源有更新,直到緩存時(shí)間截止前,客戶端無(wú)法獲取到最新的資源(除非請(qǐng)求時(shí)手動(dòng)添加no-store頭),另外大部分情況下服務(wù)器的資源無(wú)法直接確定緩存失效時(shí)間,所以使用對(duì)比緩存更靈活一些。
使用Last-Modify / If-Modify-Since頭實(shí)現(xiàn)協(xié)商緩存,具體方法是服務(wù)端響應(yīng)頭添加Last-Modify頭標(biāo)識(shí)資源的最后修改時(shí)間,單位為秒,當(dāng)客戶端再次發(fā)起請(qǐng)求時(shí)添加If-Modify-Since頭并賦值為上次請(qǐng)求拿到的Last-Modify頭的值。
服務(wù)端收到請(qǐng)求后自行判斷緩存資源是否仍然有效,如果有效則返回狀態(tài)碼304同時(shí)body體為空,否則下發(fā)最新的資源數(shù)據(jù)??蛻舳巳绻l(fā)現(xiàn)狀態(tài)碼是304,則取出本地的緩存數(shù)據(jù)作為響應(yīng)。

OKHttp不支持的緩存情況
最后需要注意的一點(diǎn)是,OKHttp默認(rèn)只支持get請(qǐng)求的緩存。
對(duì)于標(biāo)準(zhǔn)的RESTful請(qǐng)求,GET就是用來(lái)獲取數(shù)據(jù),最適合使用緩存,而對(duì)于數(shù)據(jù)的其他操作緩存意義不大或者根本不需要緩存。也是基于此在僅支持GET請(qǐng)求的條件下,OKHTTP使用request URL作為緩存的key(當(dāng)然還會(huì)經(jīng)過(guò)一系列摘要算法)。

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

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

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