Android網路開發(三、okhttp責任鏈)

語言: CN / TW / HK

theme: juejin highlight: a11y-dark


一、責任鏈模式概述

1.1 定義

  責任鏈模式是面向物件程式設計的一種軟體設計模式。在責任鏈模式裡,把每一個物件及其對下家的引用連線起來形成一條鏈,請求在這個鏈上傳遞並返回結果。每個物件在收到請求時,如果可以處理則處理並返回結果,如果一個物件不能處理該請求,那麼它會把相同的請求傳給下一個接收者,直到某一個物件完成處理。

1.2 RealCall

  在《Android網路基礎(二、okhttp&retrofit)》中分析到,okhttp執行建立連線、傳送Request、讀取Response等工作都是在RealCall類攔截器責任鏈中的各Interceptor實現的,下面是RealCall呼叫攔截鏈去獲取Response的程式碼: ```kotlin /* * okhttp業務層和網路層的橋接層 * 對建立連線、傳送請求、讀取響應、IO流進行了封裝 / class RealCall(val client: OkHttpClient, val originalRequest: Request, val forWebSocket: Boolean) : Call {

/**
* 責任鏈模式,多個攔截器組成一個攔截鏈
*/
internal fun getResponseWithInterceptorChain(): Response {
    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
    }
    // 資料傳送&讀取攔截器CallServerInterceptor
    interceptors += CallServerInterceptor(forWebSocket)
    // 攔截鏈InterceptorChain
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )
    // 攔截鏈InterceptorChain開始處理request物件
    val response = chain.proceed(originalRequest)
    return response
}

} ```   如程式碼中所示,okhttp攔截鏈有以下幾種攔截器Interceptor,按如下順序依次處理request物件: image.png

1.3 攔截器責任鏈

  攔截器責任鏈InterceptorChain呼叫proceed()方法來傳遞request物件,每個攔截器在處理request物件時如果可以處理則進行處理並返回結果,如果不能處理則繼續傳給下一個攔截器(例如如果快取攔截器CacheInterceptor可以處理則返回快取Response,否則就繼續傳遞給ConnectInterceptor執行連線建立等工作來返回Response)。
```kotlin class RealInterceptorChain( internal val call: RealCall, private val interceptors: List, private val index: Int, internal val exchange: Exchange?, internal val request: Request, internal val connectTimeoutMillis: Int, internal val readTimeoutMillis: Int, internal val writeTimeoutMillis: Int ) : Interceptor.Chain {

override fun proceed(request: Request): Response {
    check(index < interceptors.size)
    // 把當前攔截鏈複製一份,並把其index+1
    val next = copy(index = index + 1, request = request)
    // 當前指向的攔截器
    val interceptor = interceptors[index]
    // 當前攔截器進行處理:在每個攔截器處理request物件時,如果自身不能處理,則呼叫next.proceed()由下一個攔截器處理
    val response = interceptor.intercept(next)
    return response
}

} ```   攔截器責任鏈的各攔截器的主要工作總結如下所示,下面從程式碼進行分析: image.png

二、RetryAndFollowUpInterceptor

  RetryAndFollowUpInterceptor是重試和重定向等攔截器,其內部有一個while迴圈用於重試或傳送重定向請求等。 ```kotlin class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    // 重試和重定向等攔截器內部是while迴圈,在後續攔截器都未處理時會進行對應處理並迴圈
    while (true) {
        var response: Response
        var closeActiveExchange = true
        try {
            try {
                // 傳給下一個攔截器執行後續處理;
                response = realChain.proceed(request)
            } catch (e: RouteException) {
                // 路由異常時,進行相應處理程式碼並繼續迴圈,省略部分程式碼...
                continue
            } catch (e: IOException) {
                // 讀寫異常時,進行相應處理程式碼並繼續迴圈,省略部分程式碼...
                continue
            }
            val exchange = call.interceptorScopedExchange
            // 構造重試或重定向等請求
            val followUp = followUpRequest(response, exchange)
            if (followUp == null) {
                // 沒有重試或重定向等請求則返回
                return response
            }
            // 重試或重定向等請求,繼續迴圈
            request = followUp
        } finally {
            call.exitNetworkInterceptorExchange(closeActiveExchange)
        }
    }
}

/**
 * 構造後續的重試或重定向等請求
 */
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
    val route = exchange?.connection?.route()
    val responseCode = userResponse.code
    val method = userResponse.request.method
    when (responseCode) {
        HttpURLConnection.HTTP_PROXY_AUTH -> {
            return client.proxyAuthenticator.authenticate(route, userResponse)
        }
        HttpURLConnection.HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)

        StatusLine.HTTP_PERM_REDIRECT, StatusLine.HTTP_TEMP_REDIRECT, HttpURLConnection.HTTP_MULT_CHOICE,
        HttpURLConnection.HTTP_MOVED_PERM, HttpURLConnection.HTTP_MOVED_TEMP, HttpURLConnection.HTTP_SEE_OTHER -> {
            // 重定向
            return buildRedirectRequest(userResponse, method)
        }
        HttpURLConnection.HTTP_CLIENT_TIMEOUT -> {
            // 408錯誤重試;實踐中比較少見
            if (retryAfter(userResponse, 0) > 0) {
                return null
            }
            return userResponse.request
        }
        HttpURLConnection.HTTP_UNAVAILABLE -> {
            // 503錯誤重試
            if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
                return userResponse.request
            }
            return null
        }
        StatusLine.HTTP_MISDIRECTED_REQUEST -> {
            // 省略程式碼
            exchange.connection.noCoalescedConnections()
            return userResponse.request
        }
        else -> return null
    }
}

} ```

三、BridgeInterceptor

  BridgeInterceptor是橋接攔截器,是應用層request和網路層request的一個橋接器。BridgeInterceptor根據應用層的request物件複製出一個新的request物件,把業務層的request和後續處理的request隔離開,並在新的request物件中拼接Content-Type、Content-Length、Accept-Encoding等header。然後繼續向下傳遞,在下級攔截器返回reponse物件後,BridgeInterceptor支援對response解壓然後把response繼續傳遞給上級攔截器。 ```kotlin class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request()
    // 1.根據應用層的request物件複製出一個新的request物件,把業務層的request和後續處理的request隔離開
    val requestBuilder = userRequest.newBuilder()
    val body = userRequest.body
    // 2.在新的request物件中拼接Content-Type、Content-Length、Accept-Encoding等header
    if (body != null) {
        val contentType = body.contentType()
        if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString())
        }
        val contentLength = body.contentLength()
        if (contentLength != -1L) {
            requestBuilder.header("Content-Length", contentLength.toString())
            requestBuilder.removeHeader("Transfer-Encoding")
        } else {
            requestBuilder.header("Transfer-Encoding", "chunked")
            requestBuilder.removeHeader("Content-Length")
        }
    }
    if (userRequest.header("Host") == null) {
        requestBuilder.header("Host", userRequest.url.toHostHeader())
    }
    if (userRequest.header("Connection") == null) {
        requestBuilder.header("Connection", "Keep-Alive")
    }
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
        transparentGzip = true
        requestBuilder.header("Accept-Encoding", "gzip")
    }
    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
        requestBuilder.header("Cookie", cookieHeader(cookies))
    }
    if (userRequest.header("User-Agent") == null) {
        requestBuilder.header("User-Agent", userAgent)
    }

    // 3.由下一個攔截器繼續處理,並返回response物件
    val networkResponse = chain.proceed(requestBuilder.build())

    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
    // 4.根據網路層的response物件複製出一個新的response物件
    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)
    // 5.對返回的response支援gzip解壓
    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
        val responseBody = networkResponse.body
        if (responseBody != null) {
            val gzipSource = GzipSource(responseBody.source())
            val strippedHeaders = networkResponse.headers.newBuilder()
                .removeAll("Content-Encoding")
                .removeAll("Content-Length")
                .build()
            responseBuilder.headers(strippedHeaders)
            val contentType = networkResponse.header("Content-Type")
            responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
        }
    }
    return responseBuilder.build()
}

} ```

四、CacheInterceptor

  在建立OkHttpClient的時候可以傳一個快取目錄,快取攔截器CacheInterceptor就是用來判斷request是否命中了快取。 kotlin val cacheFile = File("") val okHttpClient = OkHttpClient.Builder() .connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS)) .cache(Cache(cacheFile, 1024)) .build()   如果建立OkHttpClient時設定了快取目錄,則快取攔截器CacheInterceptor會根據一定的快取策略判斷是否需要傳送請求及是否命中快取。 ```kotlin class CacheInterceptor(val cache: Cache?) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    // 1.根據request的url從cache中獲取快取
    val cacheCandidate = cache?.get(chain.request())
    val now = System.currentTimeMillis()
    // 2.根據快取策略判斷快取是否有效
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
    // 3.快取策略判定不需要傳送請求則命中快取
    if (networkRequest == null) {
        return cacheResponse!!.newBuilder()
            .cacheResponse(CacheInterceptor.stripBody(cacheResponse))
            .build().also {
                listener.cacheHit(call, it)
            }
    }
    // 4.繼續傳遞request物件
    var networkResponse = chain.proceed(networkRequest)
    // 5.返回response
    val response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()
    if (cache != null) {
        // 6.省略部分程式碼,根據快取策略新增到快取
        cache.put(response)
    }
    return response
}

} ```

五、ConnectInterceptor

  連線攔截器ConnectInterceptor用於建立連線,內部會判斷是否可以連線複用,否則執行TCP過程去建立一個網路連線(後面章節再分析詳細過程)。 kotlin object ConnectInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val realChain = chain as RealInterceptorChain // 初始化Exchange,並判斷是否可以連線複用,建立連線等操作 val exchange = realChain.call.initExchange(chain) val connectedChain = realChain.copy(exchange = exchange) // 建立連線後,繼續向下傳遞 return connectedChain.proceed(realChain.request) } }

六、CallServerInterceptor

  在建立連線後,CallServerInterceptor用來使用Okio的Sink和Source來讀寫資料,實現把request資料傳送給後端,和從後端讀取response。 ``` class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    // 1.通過Okio的Sink把RequestBody傳送給後端
    val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
    requestBody.writeTo(bufferedRequestBody)
    bufferedRequestBody.close()
    // 省略部分程式碼
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    // 2.通過Okio的Source從後端讀response
    response.newBuilder()
        .body(exchange.openResponseBody(response))
        .build()
    return response
}

} ```

七、總結

| 攔截器 | 前處理 |後處理 | |--|--|--| | 自定義攔截器 | 可自定義,如拼接公參等 | - | 重試和重定向等攔截器 | - | 重試和重定向等 | 橋接攔截器 | 添加了Content-Type、Host、Cookie等header| gzip解壓 | 快取攔截器 | 如果快取可用,則直接返回快取 | 如果伺服器返回304則返回快取。cache不為空時對response進行快取 | 連線攔截器 | 建立連線 | | 自定義網路攔截器 |- |- | 資料傳送&讀取攔截器 | okio寫資料 | okio讀資料

The End

歡迎關注我,一起解鎖更多技能:BC的掘金主頁~💐 BC的CSDN主頁~💐💐 個人資訊彙總.png

okhttp:http://square.github.io/okhttp/
Android網路開發專欄:http://juejin.cn/column/7198740450198749240