前言
之前我們結合設計模式簡單說了下OkHttp的大體流程,今天就繼續說說它的核心部分——攔截器,
因為攔截器組成的鏈其實是完成了網路通信的整個流程,所以我們今天就從這個角度說說各攔截器的功能,
首先,做一下簡單回顧,從getResponseWithInterceptorChain方法開始,
簡單回顧(getResponseWithInterceptorChain)
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
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(
interceptors = interceptors
//...
)
val response = chain.proceed(originalRequest)
}
這些攔截器會形成一條鏈,組織了請求介面的所有作業,

以上為上節內容,不了解的朋友可以回傳上一篇文章看看,
假如我來設計攔截器
先拋開攔截器的這些概念不談,我們回顧下網路通信程序,看看實作一個網路框架至少要有哪些功能,
請求程序:封裝請求報文、建立TCP連接、向連接中發送資料回應程序:從連接中讀取資料、處理決議回應報文
而之前說過攔截器的基本代碼格式是這樣:
override fun intercept(chain: Interceptor.Chain): Response {
//做事情A
response = realChain.proceed(request)
//做事情B
}
也就是分為 請求前作業,請求傳遞,獲取回應后作業 三部分,
那我們試試能不能把上面的功能分一分,設計出幾個攔截器?
攔截器1: 處理請求前的請求報文封裝,處理回應后的回應報文分析
誒,不錯吧,攔截器1就用來處理 請求報文和回應報文的一些封裝和決議作業,就叫它封裝攔截器吧,
攔截器2: 處理請求前的建立TCP連接
肯定需要一個攔截器用來建立TCP連接,但是回應后好像沒什么需要做連接方面的作業了?那就先這樣,叫它連接攔截器吧,
攔截器3:處理請求前的資料請求(寫到資料流中)處理回應后的資料獲取(從資料流拿資料)
這個攔截器就負責TCP連接后的 I/O操作,也就是從流中讀取和獲取資料,就叫它 資料IO攔截器 吧,
好了,三個攔截器好像足夠了,我得意滿滿的偷看了一眼okhttp攔截器代碼,7個???我去,,
那再思考思考??...,還有什么情況沒考慮到呢?比如失敗重試?回傳301重定向?快取的使用?用戶自己對請求的統一處理?
所以又可以模擬出幾個新的攔截器:
攔截器4:處理回應后的失敗重試和重定向功能
沒錯,剛才只考慮到請求成功,請求失敗了要不要重試呢?回應碼為301、302時候的重定向處理?這都屬于要重新請求的部分,肯定不能丟給用戶,需要網路框架自己給處理好,就叫它 重試和重定向攔截器吧,
攔截器5:處理回應前的快取復用,處理回應后的快取回應資料,
還有一個網路請求有可能的需求就是關于快取,這個快取的概念可能有些朋友了解的不多,其實它多用于瀏覽器中,
瀏覽器快取一般分為兩部分:強制快取和協商快取,
強制快取就是服務器會告訴客戶端該怎么快取,例如 cache-Control 欄位,隨便舉幾個例子:
private:所有內容只有客戶端可以快取,Cache-Control的默認取值max-age=xxx:表示快取內容將在xxx秒后失效no-cache:客戶端快取內容,但是是否使用快取則需要經過協商快取來驗證決定no-store:所有內容都不會被快取,即不使用強制快取,也不使用協商快取
協商快取就是需要客戶端和服務器進行協商后再決定是否使用快取,比如強制快取過期失效了,就要再次請求服務器,并帶上快取標志,例如Etag,
客戶端再次進行請求的時候,請求頭帶上If-None-Match,也就是之前服務器回傳的Etag值,
Etag值就是檔案的唯一標示,服務器通過某個演算法對資源進行計算,取得一串值(類似于檔案的md5值),之后將該值通過etag回傳給客戶端
然后服務器就會將Etag值和服務器本身檔案的Etag值進行比較,如果一樣則資料沒改變,就回傳304,代表你要請求的資料沒改變,你直接用就行啦,
如果不一致,就回傳新的資料,這時候的回應碼就是正常的200,
?這個攔截器就是用于處理這些情況,我們就叫它 快取攔截器 吧,
- 攔截器6: 自定義攔截器
最后就是自定義的攔截器了,要給開發者一個可以自定義的攔截器,用于統一處理請求或回應資料,
這下好像齊了,至于之前說的7個攔截器還有1個,留個懸念最后再說,
最后再給他們排個序吧:
- 1、自定義攔截器的公共引數處理,
- 2、封裝攔截器封裝請求報文
- 3、快取攔截器的快取復用,
- 4、連接攔截器建立TCP連接,
- 5、IO攔截器的資料寫入,
- 6、IO攔截器的資料讀取,
- 7、快取攔截器保存回應資料快取,
- 8、封裝攔截器分析回應報文
- 9、重試和重定向攔截器處理重試和重定向情況,
- 10、自定義攔截器統一處理回應資料,
有點繞,來張圖瞧一瞧:

所以,攔截器的順序也基本固定了:
- 1、自定義攔截器
- 2、重試和重定向攔截器
- 3、封裝攔截器
- 4、快取攔截器
- 5、連接攔截器
- 6、IO攔截器
下面具體看看吧,
自定義攔截器
在請求之前,我們一般創建自己的自定義攔截器,用于添加一些介面公共引數,比如把token加到Header中,
class MyInterceptor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request.newBuilder()
.addHeader("token", "token")
.url(url)
.build()
return chain.proceed(request)
}
要注意的是,別忘了呼叫chain.proceed,否則這條鏈就無法繼續下去了,
在獲取回應之后,我們一般用攔截器進行結果列印,比如常用的HttpLoggingInterceptor,
addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
重試和重定向攔截器(RetryAndFollowUpInterceptor)
為了方便理解,我對原始碼進行了修剪??:
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
while (true) {
try {
try {
response = realChain.proceed(request)
} catch (e: RouteException) {
//路由錯誤
continue
} catch (e: IOException) {
// 請求錯誤
continue
}
//獲取回應碼判斷是否需要重定向
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
//沒有重定向
return response
}
//賦予重定向請求,再次進入下一次回圈
request = followUp
}
}
}
}
這樣代碼就很清晰了,重試和重定向的處理都是需要重新請求,所以這里用到了while回圈,
- 當發生請求程序中錯誤的時候,就需要重試,也就是通過continue進入下一次回圈,重新走到
realChain.proceed方法進行網路請求, - 當請求結果需要
重定向的時候,就賦予新的請求,并進入下一次回圈,重新請求網路, - 當請求結果沒有重定向,那么就直接回傳
response回應結果,
封裝攔截器(BridgeInterceptor)
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//添加頭部資訊
requestBuilder.header("Content-Type", contentType.toString())
requestBuilder.header("Host", userRequest.url.toHostHeader())
requestBuilder.header("Connection", "Keep-Alive")
requestBuilder.header("Accept-Encoding", "gzip")
requestBuilder.header("Cookie", cookieHeader(cookies))
requestBuilder.header("User-Agent", userAgent)
val networkResponse = chain.proceed(requestBuilder.build())
//解壓
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
請求前的代碼很簡單,就是添加了一些必要的頭部資訊,包括Content-Type、Host、Cookie等等,封裝成一個完整的請求報文,然后交給下一個攔截器,
而獲取回應后的代碼就有點不是很明白了,gzip是啥?GzipSource又是什么類?
gzip壓縮是基于deflate中的演算法進行壓縮的,gzip會產生自己的資料格式,gzip壓縮對于所需要壓縮的檔案,首先使用LZ77演算法進行壓縮,再對得到的結果進行huffman編碼,根據實際情況判斷是要用動態huffman編碼還是靜態huffman編碼,最后生成相應的gz壓縮檔案,
簡單的說,gzip就是一種壓縮方式,可以將資料進行壓縮,在添加頭部資訊的時候就添加了這樣一個頭部:
requestBuilder.header("Accept-Encoding", "gzip")
這一句其實就是在告訴服務器,客戶端所能接受的檔案的壓縮格式,這里設定了gzip之后,服務器看到了就能把回應報文資料進行gzip壓縮再傳輸,提高傳輸效率,節省流量,
所以請求之后的這段關于gzip的處理其實就是客戶端對壓縮資料進行解壓縮,而GzipSource是okio庫里面一個進行解壓縮讀取資料的類,
快取攔截器(CacheInterceptor)
繼續看快取攔截器—CacheInterceptor,
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//取快取
val cacheCandidate = cache?.get(chain.request())
//快取策略類
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
// 如果不允許使用網路,并且快取資料為空
if (networkRequest == null && cacheResponse == null) {
return Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(HTTP_GATEWAY_TIMEOUT)//504
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build().also {
listener.satisfactionFailure(call, it)
}
}
// 如果不允許使用網路,但是有快取
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
networkResponse = chain.proceed(networkRequest)
// 如果快取不為空
if (cacheResponse != null) {
//304,表示資料未修改
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
cache.update(cacheResponse, response)
return response
}
}
//如果開發者設定了快取,則將回應資料快取
if (cache != null) {
if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
//快取header
val cacheRequest = cache.put(response)
//快取body
return cacheWritingResponse(cacheRequest, response)
}
}
return response
}
}
還是分兩部分看:
請求之前,通過request獲取了快取,然后判斷快取為空,就直接回傳code為504的結果,如果有快取并且快取可用,則直接回傳快取,請求之后,如果回傳304代表服務器資料沒修改,則直接回傳快取,如果cache不為空,那么就把response快取下來,
這樣看是不是和上面我們說過的快取機制對應上了?請求之前就是處理強制快取的情況,請求之后就會處理協商快取的情況,
但是還是有幾個問題需要弄懂:
1、快取是怎么存盤和獲取的?
2、每次請求都會去存盤和獲取快取嗎?
3、快取策略(CacheStrategy)到底是怎么處理網路和快取的?networkRequest什么時候為空?
首先,看看快取哪里取的:
val cacheCandidate = cache?.get(chain.request())
internal fun get(request: Request): Response? {
val key = key(request.url)
val snapshot: DiskLruCache.Snapshot = try {
cache[key] ?: return null
}
val entry: Entry = try {
Entry(snapshot.getSource(ENTRY_METADATA))
}
val response = entry.response(snapshot)
if (!entry.matches(request, response)) {
response.body?.closeQuietly()
return null
}
return response
}
通過cache.get方法獲取了response快取,get方法中主要是用到了請求Request的url來作為獲取快取的標志,
所以我們可以推斷,快取的獲取是通過請求的url作為key來獲取的,
那么cache又是哪里來的呢?
val cache: Cache? = builder.cache
interceptors += CacheInterceptor(client.cache)
class CacheInterceptor(internal val cache: Cache?) : Interceptor
沒錯,就是實體化CacheInterceptor的時候傳進去的,所以這個cache是需要我們創建OkHttpClient的時候設定的,比如這樣:
val okHttpClient =
OkHttpClient().newBuilder()
.cache(Cache(cacheDir, 10 * 1024 * 1024))
.build()
這樣設定之后,okhttp就知道cache存在哪里,大小為多少,然后就可以進行服務器回應的快取處理了,
所以第二個問題也解決了,并不是每次請求都會去處理快取,而是開發者需要去設定快取的存盤目錄和大小,才會針對快取進行這一系列的處理操作,
最后再看看快取策略方法 CacheStrategy.Factory().compute()
class CacheStrategy internal constructor(
val networkRequest: Request?,
val cacheResponse: Response?
)
fun compute(): CacheStrategy {
val candidate = computeCandidate()
return candidate
}
private fun computeCandidate(): CacheStrategy {
//沒有快取情況下,回傳空快取
if (cacheResponse == null) {
return CacheStrategy(request, null)
}
//...
//快取控制不是 no-cache,且未過期
if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
val builder = cacheResponse.newBuilder()
return CacheStrategy(null, builder.build())
}
return CacheStrategy(conditionalRequest, cacheResponse)
}
在這個快取策略生存的程序中,只有一種情況下會回傳快取,也就是快取控制不是no-cache,并且快取沒過期情況下,就回傳快取,然后設定networkRequest為空,
所以也就對應上一開始快取攔截器中的獲取快取后的判斷:
// 如果不允許使用網路,但是有快取,則直接回傳快取
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
連接攔截器(ConnectInterceptor)
繼續,連接攔截器,之前說了是關于TCP連接的,
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
代碼看著倒是挺少的,但其實這里面很復雜很復雜,不著急,我們慢慢說,
這段代碼就執行了一個方法就是initExchange方法:
internal fun initExchange(chain: RealInterceptorChain): Exchange {
val codec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
return result
}
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
return resultConnection.newCodec(client, chain)
}
}
好像有一點眉目了,找到一個ExchangeCodec類,并封裝成一個Exchange類,
ExchangeCodec:是一個連接所用的編碼解碼器,用于編碼HTTP請求和解碼HTTP回應,Exchange:封裝這個編碼解碼器的一個工具類,用于管理ExchangeCodec,處理實際的 I/O,
明白了,這個連接攔截器(ConnectInterceptor)就是找到一個可用連接唄,也就是TCP連接,這個連接就是用于HTTP請求和回應的,
你可以把它可以理解為一個管道,有了這個管道,才能把資料丟進去,也才可以從管道里面取資料,
而這個ExchangeCodec,編碼解碼器就是用來讀取和輸送到這個管道的一個工具,相當于把你的資料封裝成這個連接(管道)需要的格式,
我咋知道的?我貼一段ExchangeCodec代碼你就明白了:
//Http1ExchangeCodec.java
fun writeRequest(headers: Headers, requestLine: String) {
check(state == STATE_IDLE) { "state: $state" }
sink.writeUtf8(requestLine).writeUtf8("\r\n")
for (i in 0 until headers.size) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n")
}
sink.writeUtf8("\r\n")
state = STATE_OPEN_REQUEST_BODY
}
這里貼的是Http1ExchangeCodec的write代碼,也就是Http1的編碼解碼器,
很明顯,就是將Header資訊一行一行寫到sink中,然后再由sink交給輸出流,具體就不分析了,只要知道這個編碼解碼器就是用來處理連接中進行輸送的資料即可,
然后就是這個攔截器的關鍵了,連接到底是怎么獲取的呢?繼續看看:
private fun findConnection(): RealConnection {
// 1、復用當前連接
val callConnection = call.connection
if (callConnection != null) {
//檢查這個連接是否可用和可復用
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
toClose = call.releaseConnectionNoEvents()
}
return callConnection
}
//2、從連接池中獲取可用連接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
//3、從連接池中獲取可用連接(通過一組路由routes)
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
return result
}
route = localRouteSelection.next()
// 4、創建新連接
val newConnection = RealConnection(connectionPool, route)
newConnection.connect
// 5、再獲取一次連接,防止在新建連接程序中有其他競爭連接被創建了
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
return result
}
//6、還是要使用創建的新連接,放入連接池,并回傳
connectionPool.put(newConnection)
return newConnection
}
獲取連接的程序很復雜,為了方便看懂,我簡化了代碼,分成了6步,
- 1、檢查當前連接是否可用,
怎么判斷可用的?主要做了兩個判斷
1)判斷是否不再接受新的連接
2)判斷和當前請求有相同的主機名和埠號,
這倒是很好理解,要這個連接是連接的同一個地方才能復用是吧,同一個地方怎么判斷?就是判斷主機名和埠號,
還有個問題就是為什么有當前連接??明明還沒開始連接也沒有獲取連接啊,怎么連接就被賦值了?
還記得重試和重定向攔截器嗎?對了,就是當請求失敗需要重試的時候或者重定向的時候,這時候連接還在呢,是可以直接進行復用的,
- 2和3、從連接池中獲取可用連接
第2步和第3步都是從連接池獲取連接,有什么不一樣嗎?
connectionPool.callAcquirePooledConnection(address, call, null, false)
connectionPool.callAcquirePooledConnection(address, call, routes, false)
好像多了一個routes欄位?
這里涉及到HTTP/2的一個技術,叫做 HTTP/2 CONNECTION COALESCING(連接合并),什么意思呢?
假設有兩個域名,可以決議為相同的IP地址,并且是可以用相同的TLS證書(比如通配符證書),那么客戶端可以重用相同的TCP連接從這兩個域名中獲取資源,
再看回我們的連接池,這個routes就是當前域名(主機名)可以被決議的ip地址集合,這兩個方法的區別也就是一個傳了路由地址,一個沒有傳,
繼續看callAcquirePooledConnection代碼:
internal fun isEligible(address: Address, routes: List<Route>?): Boolean {
if (address.url.host == this.route().address.url.host) {
return true
}
//HTTP/2 CONNECTION COALESCING
if (http2Connection == null) return false
if (routes == null || !routeMatchesAny(routes)) return false
if (address.hostnameVerifier !== OkHostnameVerifier) return false
return true
}
1)判斷主機名、埠號等,如果請求完全相同就直接回傳這個連接,
2)如果主機名不同,還可以判斷是不是HTTP/2請求,如果是就繼續判斷路由地址,證書,如果都能匹配上,那么這個連接也是可用的,
- 4、創建新連接
如果沒有從連接池中獲取到新連接,那么就創建一個新連接,這里就不多說了,其實就是呼叫到socket.connect進行TCP連接,
- 5、再從連接池獲取一次連接,防止在新建連接程序中有其他競爭連接被創建了
創建了新連接,為什么還要去連接池獲取一次連接呢?
因為在這個程序中,有可能有其他的請求和你一起創建了新連接,所以我們需要再去取一次連接,如果有可以用的,就直接用它,防止資源浪費,
其實這里又涉及到HTTP2的一個知識點:多路復用,
簡單的說,就是不需要當前連接的上一個請求結束之后再去進行下一次請求,只要有連接就可以直接用,
HTTP/2引入二進制資料幀和流的概念,其中幀對資料進行順序標識,這樣在收到資料之后,就可以按照序列對資料進行合并,而不會出現合并后資料錯亂的情況,同樣是因為有了序列,服務器就可以并行的傳輸資料,這就是流所做的事情,
所以在HTTP/2中可以保證在同一個域名只建立一路連接,并且可以并發進行請求,
- 6、新連接放入連接池,并回傳
最后一步好理解吧,走到這里說明就要用這個新連接了,那么就把它存到連接池,回傳這個連接,
這個攔截器確實麻煩,大家好好梳理下吧,我也再來個圖:

IO攔截器(CallServerInterceptor)
連接拿到了,編碼解碼器有了,剩下的就是發資料,讀資料了,也就是跟I/O相關的作業,
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
//寫header資料
exchange.writeRequestHeaders(request)
//寫body資料
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
exchange.noRequestBody()
}
//結束請求
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
//獲取回應資料
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.build()
var code = response.code
response = response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
return response
}
}
這個攔截器 倒是沒干什么活,之前的攔截器兄弟們都把準備作業干完了,它就呼叫下exchange類的各種方法,寫入header,body,拿到code,response,
這活可干的真輕松啊,
被遺漏的自定義攔截器(networkInterceptors)
好了,最后補上這個攔截器networkInterceptors,它也是一個自定義攔截器,位于CallServerInterceptor之前,屬于倒數第二個攔截器,
那為什么OkHttp在有了一個自定義攔截器的前提下又提供了一個攔截器呢?
可以發現,這個攔截器的位置是比較深的位置,處在發送資料的前一刻,以及收到資料的第一刻,
這么敏感的位置,決定了通過這個攔截器可以看到更多的資訊,比如:
請求之前,OkHttp處理之后的請求報文資料,比如增加了各種header之后的資料,請求之后,OkHttp處理之前的回應報文資料,比如解壓縮之前的資料,
所以,這個攔截器就是用來網路除錯的,除錯比較底層、更全面的資料,
總結
最后再回顧下每個攔截器的作用:
addInterceptor(Interceptor),這是由開發者設定的,會按照開發者的要求,在所有的攔截器處理之前進行最早的攔截處理,比如一些公共引數,Header都可以在這里添加,RetryAndFollowUpInterceptor,這里會對連接做一些初始化作業,以及請求失敗的重試作業,重定向的后續請求作業,BridgeInterceptor,這里會為用戶構建一個能夠進行網路訪問的請求,同時后續作業將網路請求回來的回應Response轉化為用戶可用的Response,比如添加檔案型別,content-length計算添加,gzip解包,CacheInterceptor,這里主要是處理cache相關處理,會根據OkHttpClient物件的配置以及快取策略對請求值進行快取,而且如果本地有了可?的Cache,就可以在沒有網路互動的情況下就回傳快取結果,ConnectInterceptor,這里主要就是負責建立連接了,會建立TCP連接或者TLS連接,以及負責編碼解碼的HttpCodec,networkInterceptors,這里也是開發者自己設定的,所以本質上和第一個攔截器差不多,但是由于位置不同,用處也不同,這個位置添加的攔截器可以看到請求和回應的資料了,所以可以做一些網路除錯,CallServerInterceptor,這里就是進行網路資料的請求和回應了,也就是實際的網路I/O操作,通過socket讀寫資料,
參考
https://www.jianshu.com/p/bfb13eb3a425
https://segmentfault.com/a/1190000020386580
https://www.jianshu.com/p/02db8b55aae9
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc
拜拜
感謝大家的閱讀,有一起學習的小伙伴可以關注下我的公眾號——碼上積木????
每日一個知識點,積少成多,建立知識體系架構,
這里有一群很好的Android小伙伴,歡迎大家加入~
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/273191.html
標籤:Android

