在这个缓存策略生存的过程中,只有一种情况下会返回缓存,也就是缓存控制不是 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 RealInterceptorChainval 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类 。
ExchangeCodecExchange
明白了,这个连接拦截器(ConnectInterceptor)就是找到一个可用连接呗,也就是TCP连接,这个连接就是用于HTTP请求和响应的 。
你可以把它可以理解为一个 管道 ,有了这个管道,才能把数据丢进去,也才可以从管道里面取数据 。
而这个 ExchangeCodec ,编码解码器就是用来读取和输送到这个管道的一个工具,相当于把你的数据封装成这个连接(管道)需要的格式 。
我咋知道的?我贴一段ExchangeCodec代码你就明白了:
//Http1ExchangeCodec.JAVAfun writeRequest(headers: Headers, requestLine: String) {check(state == STATE_IDLE) { "state: $state" }sink.writeUtf8(requestLine).writeUtf8("rn")for (i in 0 until headers.size) {sink.writeUtf8(headers.name(i)).writeUtf8(": ").writeUtf8(headers.value(i)).writeUtf8("rn")}sink.writeUtf8("rn")state = STATE_OPEN_REQUEST_BODY}
这里贴的是 Http1ExchangeCodec 的write代码,也就是Http1的编码解码器 。
很明显,就是将Header信息一行一行写到sink中,然后再由sink交给输出流,具体就不分析了 。只要知道这个编码解码器就是用来处理连接中进行输送的数据即可 。
然后就是这个拦截器的关键了,连接到底是怎么获取的呢?继续看看:
private fun findConnection(): RealConnection {// 1、复用当前连接val callConnection = call.connectionif (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)判断和当前请求有相同的主机名和端口号 。
这倒是很好理解,要这个连接是连接的同一个地方才能复用是吧,同一个地方怎么判断?就是判断 主机名和端口号 。
推荐阅读
- 从零开始怎样练太极拳基本功
- 调养视网膜脱落从太极拳养身开始
- 招聘|从一线到三四线城市,中超赛区接连遇冷,陈戌源担心事情还是发生
- 五亿年后的地球 从宇宙诞生到地球灭亡
- nasa空间站直播 nasa国际空间站直播
- 路由器|坐等换路由!Wi-Fi 7加速到来:网络体验完美取代Wi-Fi 6
- 十大潮州美食推荐
- 紫沙茶罐图片,图片来源于网络
- K8S 的网络架构弄清楚了吗?
- 爱因斯坦说蜜蜂在地球上消失 爱因斯坦说蜜蜂如果从世界上消失会怎么样