前言
在上一篇文章中劲腿,我們梳理了一遍Okhttp的源碼旭绒,初步了解了基本執(zhí)行流程,是一篇總覽全局的文章焦人。拋磚引玉挥吵,通過上一篇文章我們搞明白了Okhttp的基本工作原理,也知道Okhttp網(wǎng)絡(luò)請(qǐng)求的真正執(zhí)行是通過攔截器鏈關(guān)聯(lián)的給個(gè)攔截器進(jìn)行處理的花椭,每個(gè)攔截器負(fù)責(zé)不同的功能伐债,下面我們就來一一分析每個(gè)攔截器。
攔截器責(zé)任鏈
OKHttp最核心的工作是在getResponseWithInterceptorChain()
中進(jìn)行的媚创,在分析之前蹄殃,我們先來了解什么事責(zé)任鏈模式。
責(zé)任鏈模式
責(zé)任鏈顧名思義就是由一系列的負(fù)責(zé)者構(gòu)成的一個(gè)鏈條袋倔。
為請(qǐng)求創(chuàng)建了一個(gè)接受者對(duì)象的鏈雕蔽。這種模式給予請(qǐng)求的類型,對(duì)請(qǐng)求的發(fā)送者和接收者進(jìn)行解耦宾娜。在這種模式中批狐,通常每個(gè)接收者都包含對(duì)另一個(gè)接收者的引用。如果一個(gè)對(duì)象不能處理該請(qǐng)求前塔,那么它會(huì)吧相同的請(qǐng)求傳給下一個(gè)接收者嚣艇,以此類推。
攔截器流程
先看下RealCall
的getResponseWithInterceptorChain()
华弓,攔截器的添加和連接器鏈的執(zhí)行都在此方法中食零。
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// 創(chuàng)建一系列攔截器
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
...
//攔截器鏈的執(zhí)行
val response = chain.proceed(originalRequest)
...
return response
...
}
getResponseWithInterceptorChain()
中攔截器鏈流程:
回顧下上一篇介紹的Okhttp五大默認(rèn)攔截器:
-
RetryAndFollowUpInterceptor
:第一個(gè)接觸到請(qǐng)求,最后接觸到響應(yīng)(U型調(diào)用)的默認(rèn)攔截器寂屏;負(fù)責(zé)處理錯(cuò)誤重試和重定向 贰谣。 -
BridgeInterceptor
:補(bǔ)全請(qǐng)求娜搂,并對(duì)響應(yīng)進(jìn)行額外處理。 -
CacheInterceptor
:請(qǐng)求前查詢緩存冈爹,獲得響應(yīng)并判斷是否需要緩存涌攻。 -
ConnectInterceptor
:與服務(wù)器完成TCP連接。 -
CallServerInterceptor
: 與服務(wù)器通信频伤;封裝請(qǐng)求數(shù)據(jù)與解析響應(yīng)數(shù)據(jù)(如:HTTP報(bào)文)恳谎。
RetryAndFollowUpInterceptor(重試、重定向)
如果沒有添加應(yīng)用攔截器憋肖,那么第一個(gè)攔截器就是RetryAndFollowUpInterceptor
因痛,主要作用就是:連接失敗后進(jìn)行重試、對(duì)請(qǐng)求結(jié)果跟進(jìn)后進(jìn)行重定向岸更。接著看下它的Intercept
方法:
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newRoutePlanner = true
var recoveredFailures = listOf<IOException>()
while (true) {
//準(zhǔn)備連接
call.enterNetworkInterceptorExchange(request, newRoutePlanner, chain)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
//繼續(xù)執(zhí)行下一個(gè)Interceptor
response = realChain.proceed(request)
newRoutePlanner = true
} catch (e: IOException) {
// 嘗試與服務(wù)器通信失敗鸵膏。請(qǐng)求可能已經(jīng)發(fā)送
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newRoutePlanner = false
continue
}
// 清除下游攔截器的其他請(qǐng)求標(biāo)頭、cookie 等
response = response.newBuilder()
.request(request)
.priorResponse(priorResponse?.stripBody())
.build()
val exchange = call.interceptorScopedExchange
//跟進(jìn)結(jié)果怎炊,主要作用是根據(jù)響應(yīng)碼處理請(qǐng)求谭企,返回Request不為空時(shí)則進(jìn)行重定向處理-拿到重定向的request
val followUp = followUpRequest(response, exchange)
//followUp為空,不需要重試评肆,直接返回
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body.closeQuietly()
//最多重試20次
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
//賦值债查,以便再次請(qǐng)求
request = followUp
priorResponse = response
} finally {
// 請(qǐng)求沒成功,釋放資源瓜挽。
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
重試
可以看到請(qǐng)求階段發(fā)生了IOException(老版本還會(huì)捕獲RouteException)盹廷,會(huì)通過recover
方法判斷是否能夠進(jìn)行重試,若返回true
久橙,則表示允許重試俄占。
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// 應(yīng)用層禁止重試,就不重試
if (!client.retryOnConnectionFailure) return false
// 不能再次發(fā)送請(qǐng)求淆衷,就不重試(requestSendStarted只在http2的io異常中為true缸榄,先不管http2)
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// 發(fā)生的異常是致命的,就不重試
if (!isRecoverable(e, requestSendStarted)) return false
// 沒有路由可以嘗試祝拯,就不重試
if (!call.retryAfterFailure()) return false
return true
}
接下來看一下isRecoverable
中判斷了哪些異常甚带。
private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
// 出現(xiàn)協(xié)議異常,不能重試
if (e is ProtocolException) {
return false
}
// 如果發(fā)生中斷( requestSendStarted認(rèn)為它一直為false(不管http2))鹿驼,不要恢復(fù)欲低;但如果連接到路由器超時(shí)(socket超時(shí))辕宏,判定可以重試
if (e is InterruptedIOException) {
return e is SocketTimeoutException && !requestSendStarted
}
//SSL握手異常中畜晰,證書出現(xiàn)問題,不能重試
if (e is SSLHandshakeException) {
if (e.cause is CertificateException) {
return false
}
}
//SSL握手未授權(quán)異常 不能重試
if (e is SSLPeerUnverifiedException) {
return false
}
return true
}
- 協(xié)議異常:例如你的請(qǐng)求或者服務(wù)器的響應(yīng)本身就存在問題瑞筐,沒有按照http協(xié)議來定義數(shù)據(jù)凄鼻,直接無法重試。
- 超時(shí)異常:可能由于網(wǎng)絡(luò)波動(dòng)造成了Socket管道的超時(shí),如果有下一個(gè)路由的話块蚌,嘗試下一個(gè)路由闰非。
- SSL證書異常/SSL驗(yàn)證失敗異常:證書驗(yàn)證失敗或者缺少證書,無法重試峭范。
簡(jiǎn)單總結(jié)一下财松,首先在用戶未禁止重試的前提下,如果出現(xiàn)了某些異常纱控,則會(huì)在isRecoverable
中進(jìn)行判斷辆毡,進(jìn)過上面一系列判斷后,如果允許進(jìn)行重試甜害,就會(huì)再檢查當(dāng)前是否有可用路由進(jìn)行連接重試舶掖。比如DNS對(duì)域名解析后可能會(huì)返回多個(gè)IP,在一個(gè)IP失敗后尔店,嘗試另一個(gè)IP進(jìn)行重試眨攘。
重定向
如果realChain.proceed沒有發(fā)生異常,返回了結(jié)果response嚣州,就會(huì)使用followUpRequest方法跟進(jìn)結(jié)果并構(gòu)建重定向request鲫售。 如果不用跟進(jìn)處理(例如響應(yīng)碼是200),則返回null避诽」昊ⅲ看下followUpRequest方法:
@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
// 407 客戶端使用了HTTP代理服務(wù)器,在請(qǐng)求頭中添加 “Proxy-Authorization”沙庐,讓代理服務(wù)器授權(quán)
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
// 401 需要身份驗(yàn)證 有些服務(wù)器接口需要驗(yàn)證使用者身份 在請(qǐng)求頭中添加 “Authorization”
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
//308 永久重定向 ,307 臨時(shí)重定向,300,301,302,303
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// 408 客戶端請(qǐng)求超時(shí)
HTTP_CLIENT_TIMEOUT -> {
// 408 算是連接失敗了鲤妥,所以判斷用戶是不是允許重試
if (!client.retryOnConnectionFailure) {
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
// 如果是本身這次的響應(yīng)就是重新請(qǐng)求的產(chǎn)物同時(shí)上一次之所以重請(qǐng)求還是因?yàn)?08,那我們這次不再重請(qǐng)求了
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
return null
}
// 如果服務(wù)器告訴我們了 Retry-After 多久后重試拱雏,那框架不管了棉安。
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
// 503 服務(wù)不可用 和408差不多,但是只在服務(wù)器告訴你 Retry-After:0(意思就是立即重試) 才重請(qǐng)求
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
return userResponse.request
}
return null
}
HTTP_MISDIRECTED_REQUEST -> {
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
主要就是根據(jù)響應(yīng)碼判斷如果需要重定向铸抑,如果此方法返回空贡耽,那就表示不需要再重定向了,直接返回響應(yīng)鹊汛;但是如果返回非空蒲赂,那就要重新請(qǐng)求返回的Request
,但是需要注意的是刁憋,followup`在攔截器中定義的最大次數(shù)為20次滥嘴。
總結(jié)
- 如果沒有添加應(yīng)用攔截器,那么
RetryAndFollowUpInterceptor
就是第一個(gè)攔截器至耻,主要功能是判斷是否需要重試和重定向若皱。 - 如果請(qǐng)求階段發(fā)生了IOException镊叁,就會(huì)通過
recover
方法判斷是進(jìn)行連接重試。 - 重定向發(fā)生在重試的判定之后走触,如果不滿足重試的條件晦譬,還需要進(jìn)一步調(diào)用
followUpRequest
根據(jù)Response
的響應(yīng)碼(當(dāng)然,如果直接請(qǐng)求失敗互广,Response
都不存在就會(huì)拋出異常)判斷是否重定向敛腌;followup
最大發(fā)生次數(shù)20次。
BridgeInterceptor(橋接攔截器)
BridgeInterceptor
惫皱,連接應(yīng)用程序和服務(wù)器的橋梁迎瞧,我們發(fā)出的請(qǐng)求會(huì)經(jīng)過它的處理才能發(fā)給服務(wù)器,比如設(shè)置請(qǐng)求內(nèi)容的長度逸吵,編碼凶硅,gzip壓縮,cookie等扫皱;獲取響應(yīng)后保存cookie足绅、解壓等操作。相對(duì)比較簡(jiǎn)單韩脑。
首先氢妈,chain.proceed()執(zhí)行前,對(duì)請(qǐng)求頭進(jìn)行補(bǔ)全段多,補(bǔ)全請(qǐng)求頭如下:
請(qǐng)求頭 | 說明 |
---|---|
Content-Type |
請(qǐng)求體類型,如:application/x-www-form-urlencoded
|
Content-Length /Transfer-Encoding
|
請(qǐng)求體解析方式 |
Host |
請(qǐng)求的主機(jī)站點(diǎn) |
Connection: Keep-Alive |
保持長連接 |
Accept-Encoding: gzip |
接受響應(yīng)支持gzip壓縮 |
Cookie |
cookie身份辨別 |
User-Agent |
請(qǐng)求的用戶信息首量,如:操作系統(tǒng)、瀏覽器等 |
在補(bǔ)全了請(qǐng)求頭后交給下一個(gè)攔截器處理进苍,得到響應(yīng)后加缘,主要干兩件事情:
- 先把響應(yīng)header中的cookie存入cookieJar(如果有),在下次請(qǐng)求則會(huì)讀取對(duì)應(yīng)的數(shù)據(jù)設(shè)置進(jìn)入請(qǐng)求頭觉啊,默認(rèn)的
CookieJar
不提供實(shí)現(xiàn)拣宏,需要我們?cè)诔跏蓟疧khttpClient時(shí)配置我們自己的cookieJar。 - 如果使用gzip返回的數(shù)據(jù)杠人,則使用
GzipSource
包裝便于解析勋乾。
總結(jié)
- 對(duì)用戶構(gòu)建的
Request
進(jìn)行添加或者刪除相關(guān)頭部信息,以轉(zhuǎn)化成能夠真正進(jìn)行網(wǎng)絡(luò)請(qǐng)求的Request
嗡善。 - 將符合網(wǎng)絡(luò)請(qǐng)求規(guī)范的Request交給下一個(gè)攔截器處理辑莫,并獲取
Response
。 - 如果響應(yīng)體經(jīng)過了gzip壓縮罩引,那就需要解壓各吨,再構(gòu)建成用戶可用的
Response
并返回。
CacheInterceptor(緩存攔截器)
CacheInterceptor
,在發(fā)出請(qǐng)求前蜒程,先判斷是否命中緩存绅你,如果命中則可以不請(qǐng)求,直接使用緩存的響應(yīng)(默認(rèn)只會(huì)對(duì)Get請(qǐng)求進(jìn)行緩存)昭躺;如果未命中則進(jìn)行網(wǎng)絡(luò)請(qǐng)求忌锯,并將結(jié)果緩存,等待下次請(qǐng)求被命中领炫。
先來看下CacheInterceptor
的intercept
方法代碼:
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
// 用request的url 從緩存中獲取Response作為候選(CacheStrategy決定是否使用)
val cacheCandidate = cache?.get(chain.request())
val now = System.currentTimeMillis()
// 根據(jù)request偶垮、候選Response 獲取緩存策略。
// 緩存策略 將決定是否使用緩存:strategy.networkRequest為null帝洪,不適用網(wǎng)絡(luò)似舵;
// strategy.cacheResponse為null,不使用緩存
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
// 根據(jù)緩存策略更新統(tǒng)計(jì)指標(biāo):請(qǐng)求次數(shù)葱峡、網(wǎng)絡(luò)請(qǐng)求次數(shù)砚哗、使用緩存次數(shù)
cache?.trackResponse(strategy)
val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
if (cacheCandidate != null && cacheResponse == null) {
// The cache candidate wasn't applicable. Close it.
cacheCandidate.body.closeQuietly()
}
// 網(wǎng)絡(luò)請(qǐng)求、緩存 都不能用砰奕,就返回504
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)
.message("Unsatisfiable Request (only-if-cached)")
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// 如果不用網(wǎng)絡(luò)蛛芥,cacheResponse肯定不為空了,那么就使用緩存了军援,結(jié)束了
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(cacheResponse.stripBody())
.build().also {
listener.cacheHit(call, it)
}
}
if (cacheResponse != null) {
listener.cacheConditionalHit(call, cacheResponse)
} else if (cache != null) {
listener.cacheMiss(call)
}
//到這里仅淑,networkRequest != null (cacheResponse可能為null,也可能!null)
//networkRequest != null,就要進(jìn)行網(wǎng)絡(luò)請(qǐng)求了胸哥, 所以攔截器鏈就繼續(xù)往下處理了
var networkResponse: Response? = null
try {
networkResponse = chain.proceed(networkRequest)
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body.closeQuietly()
}
}
// 如果網(wǎng)絡(luò)請(qǐng)求返回304涯竟,表示服務(wù)端資源沒有修改,那么就結(jié)合網(wǎng)絡(luò)響應(yīng)和緩存響應(yīng)空厌,然后更新緩存->返回->結(jié)束
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))//結(jié)合header
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)//請(qǐng)求事件
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)//接受事件
.cacheResponse(cacheResponse.stripBody())
.networkResponse(networkResponse.stripBody())
.build()
networkResponse.body.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
//如果是非304庐船,說明服務(wù)端資源有更新,就關(guān)閉緩存body
cacheResponse.body.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(cacheResponse?.stripBody())
.networkResponse(networkResponse.stripBody())
.build()
if (cache != null) {
//網(wǎng)絡(luò)響應(yīng)可緩存(請(qǐng)求和響應(yīng)的頭Cache-Control都不是'no-store')
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
// 寫入緩存
val cacheRequest = cache.put(response)
return cacheWritingResponse(cacheRequest, response).also {
if (cacheResponse != null) {
// This will log a conditional cache miss only.
listener.cacheMiss(call)
}
}
}
//Okhttp默認(rèn)只會(huì)對(duì)get請(qǐng)求進(jìn)行緩存嘲更,因?yàn)間et請(qǐng)求的數(shù)據(jù)一般是比較持久的醉鳖,而post一般是交互操作,沒太大意義進(jìn)行緩存
//不是get請(qǐng)求就移除緩存
if (HttpMethod.invalidatesCache(networkRequest.method)) {
try {
cache.remove(networkRequest)
} catch (_: IOException) {
// The cache cannot be written.
}
}
}
return response
}
大概梳理一下步驟:
從緩存中獲取對(duì)應(yīng)請(qǐng)求的響應(yīng)緩存
-
創(chuàng)建
CacheStrategy
哮内,創(chuàng)建時(shí)會(huì)判斷是否能夠使用緩存盗棵,在CacheStrategy
中有兩個(gè)成員:networkRequest
和cacheResponse
。他們有如下組合進(jìn)行判斷:networkRequest cacheResponse 說明 Null Null 網(wǎng)絡(luò)請(qǐng)求北发、緩存 都不能用纹因,okhttp直接返回504 Null Not Null 直接使用緩存 Not Null Null 向服務(wù)器發(fā)起請(qǐng)求 Not Null Not Null 向服務(wù)器發(fā)起請(qǐng)求,若得到響應(yīng)碼為304(無修改)琳拨,則更新緩存響應(yīng)并返回 交給下一個(gè)責(zé)任鏈繼續(xù)處理
后續(xù)工作瞭恰,返回304則用緩存的響應(yīng);否則使用網(wǎng)絡(luò)響應(yīng)并緩存本次響應(yīng)(只緩存Get請(qǐng)求的響應(yīng))
緩存攔截器中判斷使用緩存或者請(qǐng)求服務(wù)器都是通過CacheStrategy
判斷狱庇。分析CacheStrategy
前我們先來了解下Http的緩存機(jī)制惊畏,以便更好的理解:
首次請(qǐng)求:
第二次請(qǐng)求:
上面兩張圖是對(duì)http緩存機(jī)制的一個(gè)總結(jié):根據(jù) 緩存是否過期恶耽、過期后是否有修改 來決定 請(qǐng)求是否使用緩存。詳細(xì)內(nèi)容可看這篇文章: 徹底弄懂HTTP緩存機(jī)制及原理颜启。
回過頭我們?cè)賮砜?code>CacheStrategy偷俭,它的生成代碼看起來很簡(jiǎn)單,只有一行代碼:
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
把請(qǐng)求request
缰盏、獲選緩存cacheCandidate
傳入工廠類Factory
涌萤,然后調(diào)用compute()方法。
class Factory(
private val nowMillis: Long,
internal val request: Request,
private val cacheResponse: Response?
) {
init {
if (cacheResponse != null) {
/*獲取候選緩存的請(qǐng)求時(shí)間口猜、響應(yīng)時(shí)間负溪,從header中獲取 過期時(shí)間、修改時(shí)間济炎、資源標(biāo)記等(如果有)*/
this.sentRequestMillis = cacheResponse.sentRequestAtMillis
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
val headers = cacheResponse.headers
for (i in 0 until headers.size) {
val fieldName = headers.name(i)
val value = headers.value(i)
when {
fieldName.equals("Date", ignoreCase = true) -> {
servedDate = value.toHttpDateOrNull()
servedDateString = value
}
fieldName.equals("Expires", ignoreCase = true) -> {
expires = value.toHttpDateOrNull()
}
fieldName.equals("Last-Modified", ignoreCase = true) -> {
lastModified = value.toHttpDateOrNull()
lastModifiedString = value
}
fieldName.equals("ETag", ignoreCase = true) -> {
etag = value
}
fieldName.equals("Age", ignoreCase = true) -> {
ageSeconds = value.toNonNegativeInt(-1)
}
}
}
}
}
/** Returns a strategy to satisfy [request] using [cacheResponse]. */
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
}
Factory
構(gòu)造方法內(nèi)川抡,獲取了候選響應(yīng)的請(qǐng)求時(shí)間、響應(yīng)時(shí)間须尚、過期時(shí)長猖腕、修改時(shí)間、資源標(biāo)記等恨闪。
compute
方法內(nèi)部先調(diào)用了computeCandidate()
獲取到緩存策略實(shí)例倘感,先跟進(jìn)到computeCandidate
方法中:
/** Returns a strategy to use assuming the request can use the network. */
private fun computeCandidate(): CacheStrategy {
// 沒有緩存——進(jìn)行網(wǎng)絡(luò)請(qǐng)求
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
//https請(qǐng)求,但沒有握手——進(jìn)行網(wǎng)絡(luò)請(qǐng)求
if (request.isHttps && cacheResponse.handshake == null) {
return CacheStrategy(request, null)
}
// 網(wǎng)絡(luò)響應(yīng)不可緩存(請(qǐng)求或響應(yīng)頭Cache-Control是'no-store')——進(jìn)行網(wǎng)絡(luò)請(qǐng)求
if (!isCacheable(cacheResponse, request)) {
return CacheStrategy(request, null)
}
//請(qǐng)求頭Cache-Control是no-cache或者請(qǐng)求頭有"If-Modified-Since"或"If-None-Match"——進(jìn)行網(wǎng)絡(luò)請(qǐng)求
//意思是不使用緩存或者請(qǐng)求手動(dòng)添加了頭部"If-Modified-Since"或"If-None-Match"
val requestCaching = request.cacheControl
if (requestCaching.noCache || hasConditions(request)) {
return CacheStrategy(request, null)
}
val responseCaching = cacheResponse.cacheControl
//緩存的年齡
val ageMillis = cacheResponseAge()
//緩存的有效期
var freshMillis = computeFreshnessLifetime()
//比較請(qǐng)求頭里有效期咙咽,取較小值
if (requestCaching.maxAgeSeconds != -1) {
freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
}
//可接受緩存的最小剩余時(shí)間(min-fresh表示客戶端不愿意接收剩余有效期<=min-fresh的緩存)
var minFreshMillis: Long = 0
if (requestCaching.minFreshSeconds != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
}
//可接受緩存的最大過期時(shí)間(max-stale指令表示了客戶端愿意接收一個(gè)過期了的緩存老玛,例如:過期了1小時(shí)還可以用)
var maxStaleMillis: Long = 0
// 第一個(gè)判斷:是否要求必須去服務(wù)器驗(yàn)證資源狀態(tài)
// 第二個(gè)判斷:獲取max-stale值,如果不等于-1钧敞,說明緩存過期后還能使用指定的時(shí)長
if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
}
//如果響應(yīng)頭沒有要求忽略本地緩存并且整合后的緩存年齡小于整合后的過期時(shí)間蜡豹,那么緩存就可以用
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
//沒有滿足“可接受的最小剩余有效時(shí)間”,加個(gè)110警告
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
}
val oneDayMillis = 24 * 60 * 60 * 1000L
//isFreshnessLifetimeHeuristic表示沒有過期時(shí)間且緩存的年齡大于一天溉苛,就加個(gè)113警告
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
}
return CacheStrategy(null, builder.build())
}
//到這里镜廉,說明緩存是過期的
//然后找緩存里的Etag、lastModified愚战、servedDate
val conditionName: String
val conditionValue: String?
when {
etag != null -> {
conditionName = "If-None-Match"
conditionValue = etag
}
lastModified != null -> {
conditionName = "If-Modified-Since"
conditionValue = lastModifiedString
}
servedDate != null -> {
conditionName = "If-Modified-Since"
conditionValue = servedDateString
}
//都沒有娇唯,就執(zhí)行常規(guī)網(wǎng)絡(luò)請(qǐng)求
else -> return CacheStrategy(request, null) // No condition! Make a regular request.
}
// 如果有,就添加到網(wǎng)絡(luò)請(qǐng)求的頭部
val conditionalRequestHeaders = request.headers.newBuilder()
conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
val conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build()
//conditionalRequest(條件網(wǎng)絡(luò)請(qǐng)求):有緩存但過期了寂玲,去請(qǐng)求網(wǎng)絡(luò)詢問服務(wù)器是否能用塔插,能用側(cè)返回304,不能則正常執(zhí)行網(wǎng)絡(luò)請(qǐng)求
return CacheStrategy(conditionalRequest, cacheResponse)
}
同樣經(jīng)過一系列判斷:
- 沒有緩存拓哟、https請(qǐng)求但沒有握手想许、手動(dòng)設(shè)置不緩存、忽略緩存或者手動(dòng)配置緩存過期,都是直接進(jìn)行網(wǎng)絡(luò)請(qǐng)求流纹。
- 1中都不滿足時(shí)糜烹,如果緩存沒有過期,則使用緩存(可能會(huì)添加警告)漱凝。
- 1中都不滿足時(shí)疮蹦,如果緩存過期了,但響應(yīng)頭有Etag碉哑,Last-Modified,Date亮蒋,就添加這些header進(jìn)行條件網(wǎng)絡(luò)請(qǐng)求扣典。
- 1中都不滿足時(shí),如果緩存過期了慎玖,且響應(yīng)頭沒有設(shè)置Etag贮尖,Last-Modified,Date趁怔,就進(jìn)行常規(guī)網(wǎng)絡(luò)請(qǐng)求湿硝。
回頭接著再看compute()
方法:
/** Returns a strategy to satisfy [request] using [cacheResponse]. */
fun compute(): CacheStrategy {
val candidate = computeCandidate()
// We're forbidden from using the network and the cache is insufficient.
if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
return CacheStrategy(null, null)
}
return candidate
}
}
computeCandidate()
獲取到緩存策略候選后,又進(jìn)行了一個(gè)判斷:使用網(wǎng)絡(luò)請(qǐng)求 但是 原請(qǐng)求配置了只能使用緩存润努,按照上面的分析关斜,此時(shí)即使有緩存,也是過期的緩存铺浇,所以又new了實(shí)例痢畜,兩個(gè)值都為null。
到這里okhttp的緩存機(jī)制源碼就看完了鳍侣。okhttp的緩存機(jī)制是符合開頭http的緩存機(jī)制那兩張圖的丁稀,只是增加很多細(xì)節(jié)判斷。
另外注意倚聚,緩存的讀寫是通過 InternalCache
完成的线衫。InternalCache
是在創(chuàng)建CacheInterceptor
實(shí)例時(shí) 用client.internalCache()
作為參數(shù)傳入。而InternalCache
是okhttp內(nèi)部使用惑折,類似一個(gè)代理授账,InternalCache
的實(shí)例是 類的屬性。Cache
是我們初始化 OkHttpClient
時(shí)傳入的惨驶。所以如果沒有傳入Cache
實(shí)例是沒有緩存功能的矗积。
val client = OkHttpClient.Builder()
.cache(Cache(getExternalCacheDir(),500 * 1024 * 1024))
.build()
緩存的增刪改查,Cache
是通過okhttp內(nèi)部的DiskLruCache
實(shí)現(xiàn)的敞咧,原理和jakewharton的DiskLruCache
是一致的棘捣,這里就不再敘述。
結(jié)語
本篇文章講解了三個(gè)攔截器工作原理,分別是:RetryAndFollowUpInterceptor乍恐、BridgeInterceptor评疗、CacheInterceptor。由于篇幅原因茵烈,剩下兩個(gè)攔截器:ConnectInterceptor百匆、CallServerInterceptor,放在下一篇進(jìn)行講解呜投。