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

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

概述

该拦截器负责建立与服务器的连接,但是并不与服务器进行IO交互,IO交互是CallServerInterceptor的职责。生成了一个Exchange类。

对于Exchange这个类的而言,我将其理解为一个包含了如何处理Http编码与解码的类,也是由它来指定使用的HTTP协议版本。

流程图

流程图

源码分析

在该拦截器的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<Route> 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对象交给后续的拦截器去和服务器进行数据的读写。

发布了24 篇原创文章 · 获赞 7 · 访问量 8682

猜你喜欢

转载自blog.csdn.net/d745282469/article/details/104296848
今日推荐