OkHttp|【Android】OkHttp系列(五)(连接拦截器ConnectInterceptor)

该系列OkHttp源码分析基于OkHttp3.14.0版本

文章目录

  • 概述
  • 流程图
  • 源码分析

概述 该拦截器负责建立与服务器的连接,但是并不与服务器进行IO交互,IO交互是CallServerInterceptor的职责。生成了一个Exchange类。
【OkHttp|【Android】OkHttp系列(五)(连接拦截器ConnectInterceptor)】对于Exchange这个类的而言,我将其理解为一个包含了如何处理Http编码与解码的类,也是由它来指定使用的HTTP协议版本。
流程图 OkHttp|【Android】OkHttp系列(五)(连接拦截器ConnectInterceptor)
文章图片

源码分析 在该拦截器的intercept方法中,可以看到关键的一句代码:
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

进入这个newExchange()看看:
/** Returns a new exchange to carry a new request and response. *返回一个新的Exchange以携带新的请求和响应。 */ Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) { synchronized (connectionPool) { if (noMoreExchanges) throw new IllegalStateException("released"); if (exchange != null) throw new IllegalStateException("exchange != null"); }// 用于编码和解码http协议的 ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks); Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec); synchronized (connectionPool) { this.exchange = result; this.exchangeRequestDone = false; this.exchangeResponseDone = false; return result; } }

可以看到,这个方法的关键点在于exchangeFinder.find()这个方法中,进去:
public ExchangeCodec find( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { ...省略部分代码try { //找到或创建一个连接 RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); return resultConnection.newCodec(client, chain); ...省略部分代码 }

对于这个方法,关键在于生成了一个连接RealConnection,然后由这个连接作为参数生成了一个ExchangeCodec,而生成连接的方法在findHealthyConnection中。那么我们继续跟进:
while (true) { //生成或复用一个连接 RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); // If this is a brand new connection, we can skip the extensive health checks. // 如果这是一个全新的连接,我们可以跳过广泛的运行状况检查。 synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } }// Do a (potentially slow) check to confirm that the pooled connection is still good. If it // isn't, take it out of the pool and start again. // 进行(可能很慢)检查以确认池中连接仍然良好。 如果不是,请将其从池中取出并重新开始。 if (!candidate.isHealthy(doExtensiveHealthChecks)) { candidate.noNewExchanges(); continue; }return candidate; }

一个while(true)死循环,调用了findConnection(),根据名称很容易可以猜到,这里应该就是最终生成或复用连接的地方了。继续跟进:
/** * Returns a connection to host a new stream. This prefers the existing connection if it exists, * then the pool, finally building a new connection. * 返回用于托管新流的连接。 如果存在现有连接,则首选现有连接,然后是池,最后建立一个新连接。 */ private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException { boolean foundPooledConnection = false; RealConnection result = null; Route selectedRoute = null; RealConnection releasedConnection; Socket toClose; synchronized (connectionPool) { if (transmitter.isCanceled()) throw new IOException("Canceled"); hasStreamFailure = false; // This is a fresh attempt. 新一次尝试Route previousRoute = retryCurrentRoute() ? transmitter.connection.route() : null; // Attempt to use an already-allocated connection. We need to be careful here because our // already-allocated connection may have been restricted from creating new exchanges. // 尝试使用已分配的连接。 我们在这里需要小心,因为我们已经分配的连接可能受到限制,无法创建新的交换。 releasedConnection = transmitter.connection; toClose = transmitter.connection != null && transmitter.connection.noNewExchanges ? transmitter.releaseConnectionNoEvents() : null; if (transmitter.connection != null) { // We had an already-allocated connection and it's good. // 我们已经分配了一个连接,很好。 result = transmitter.connection; releasedConnection = null; }if (result == null) { // 没有已分配的链接,尝试从链接池冲找一个 // Attempt to get a connection from the pool. if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) { foundPooledConnection = true; result = transmitter.connection; } else { selectedRoute = previousRoute; } } } closeQuietly(toClose); if (releasedConnection != null) { eventListener.connectionReleased(call, releasedConnection); } if (foundPooledConnection) { eventListener.connectionAcquired(call, result); } if (result != null) { // If we found an already-allocated or pooled connection, we're done. // 如果我们发现一个已经分配或连接的连接,就完成了。 return result; }// If we need a route selection, make one. This is a blocking operation. // 如果需要路线选择,请选择一个。 这是一个阻塞操作。 boolean newRouteSelection = false; if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) { newRouteSelection = true; routeSelection = routeSelector.next(); }List routes = null; synchronized (connectionPool) { if (transmitter.isCanceled()) throw new IOException("Canceled"); if (newRouteSelection) { // Now that we have a set of IP addresses, make another attempt at getting a connection from // the pool. This could match due to connection coalescing. // 现在我们有了一组IP地址,请再次尝试从池中获取连接。 由于连接合并,这可能匹配。 routes = routeSelection.getAll(); if (connectionPool.transmitterAcquirePooledConnection( address, transmitter, routes, false)) { foundPooledConnection = true; result = transmitter.connection; } }if (!foundPooledConnection) { if (selectedRoute == null) { selectedRoute = routeSelection.next(); }// Create a connection and assign it to this allocation immediately. This makes it possible // for an asynchronous cancel() to interrupt the handshake we're about to do. // 创建一个连接,并将其立即分配给该分配。 这使得异步cancel()可以中断我们将要进行的握手。 result = new RealConnection(connectionPool, selectedRoute); connectingConnection = result; } }// If we found a pooled connection on the 2nd time around, we're done. // 如果我们在第二次发现池化连接,就完成了。 if (foundPooledConnection) { eventListener.connectionAcquired(call, result); return result; }// Do TCP + TLS handshakes. This is a blocking operation. // 执行TCP + TLS握手。 这是一个阻塞操作。 result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); connectionPool.routeDatabase.connected(result.route()); Socket socket = null; synchronized (connectionPool) { connectingConnection = null; // Last attempt at connection coalescing, which only occurs if we attempted multiple // concurrent connections to the same host. // 最后一次尝试进行连接合并,只有在我们尝试到同一主机的多个并发连接时才会发生。 if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) { // We lost the race! Close the connection we created and return the pooled connection. // 我们输了比赛! 关闭我们创建的连接并返回池连接。 result.noNewExchanges = true; socket = result.socket(); result = transmitter.connection; } else { connectionPool.put(result); transmitter.acquireConnectionNoEvents(result); } } closeQuietly(socket); eventListener.connectionAcquired(call, result); return result; }

代码比较长,基本逻辑为寻找一个可复用的连接,如果找到了就返回,没找到则新建一个连接并放入连接池然后返回。
连接获取到了之后,下一步就是构造一个ExchangeCodec对象了。那么ExchangeCodec是用来干啥的呢?根据官方注释:
Encodes HTTP requests and decodes HTTP responses
编码HTTP请求并解码HTTP响应
里面封装了由Okio实现的流读写,利用它,我们就可以操作向服务器写数据以及读取服务器返回的数据。
进入newCodec()方法,我们可以看到,会返回HTTP1和HTTP2两个不同的ExchangeCodec,具体返回谁的话是根据http2Connection是否为null来判断的。
ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException { if (http2Connection != null) { return new Http2ExchangeCodec(client, this, chain, http2Connection); } else { socket.setSoTimeout(chain.readTimeoutMillis()); source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS); sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS); return new Http1ExchangeCodec(client, this, source, sink); } }

http2Connection是否为null是根据协议来进行判断的,具体的话这里就不展开了,主要涉及几个方法startHttp2()establishProtocol()connect()
现在有了ExchangeCodec对象后回到我们的newExchange()方法中,有了ExchangeCodec后,我们实例化了一个新的Exchange对象,该对象可以理解为一个工具人,里面包含了一个请求的所有信息。
然后将这个Exchange对象交给后续的拦截器去和服务器进行数据的读写。

    推荐阅读