Android网络开发(三、okhttp责任链)

一、责任链模式概述

1.1 定义

  责任链模式是面向对象程式设计的一种软件设计模式。在责任链模式里,把每一个对象及其对下家的引用连接起来形成一条链,请求在这个链上传递并返回结果。每个对象在收到请求时,如果可以处理则处理并返回结果,如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,直到某一个对象完成处理。

1.2 RealCall

  在《Android网络基础(二、okhttp&retrofit)》中分析到,okhttp执行建立连接、发送Request、读取Response等工作都是在RealCall类拦截器责任链中的各Interceptor实现的,下面是RealCall调用拦截链去获取Response的代码:

/**
 * 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)。

class RealInterceptorChain(
    internal val call: RealCall,
    private val interceptors: List<Interceptor>,
    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循环用于重试或发送重定向请求等。

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继续传递给上级拦截器。

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是否命中了缓存。

val cacheFile = File("")
val okHttpClient = OkHttpClient.Builder()
    .connectionPool(ConnectionPool(2, 35, TimeUnit.SECONDS))
    .cache(Cache(cacheFile, 1024))
    .build()

  如果创建OkHttpClient时设置了缓存目录,则缓存拦截器CacheInterceptor会根据一定的缓存策略判断是否需要发送请求及是否命中缓存。

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过程去建立一个网络连接(后面章节再分析详细过程)。

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等headergzip解压
缓存拦截器如果缓存可用,则直接返回缓存如果服务器返回304则返回缓存。cache不为空时对response进行缓存
连接拦截器建立连接
自定义网络拦截器--
数据发送&读取拦截器okio写数据okio读数据

The End

欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
请添加图片描述

okhttp:https://square.github.io/okhttp/

Android网络开发专栏:https://juejin.cn/column/7198740450198749240

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值