OkHttp源码分析——ConnectInterceptor拦截器

再看ConnectInterceptor拦截器之前应该先看连接池
ConnectInterceptor拦截器应该算几个拦截器中较为复杂的,它的拦截方法如下:

 @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //从拦截器链获取流对象
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //通过流对象创建了HttpCodec对象
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
   //调用下一个拦截器并且将这些对象传过去
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

从拦截器的方法来看代码好像很少,主要就是获取了在RetryAndFollowUpInterceptor拦截器中创建的流对象,然后利用它的newStream方法创建了HttpCodec对象,最后将它们传给下一个拦截器。HttpCodec这个类是用来对请求进行编码,对响应进行解码的。
在看看StreamAllocation.newStream()这个方法干了什么?

 public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
     //省略部分代码
    try {
    //获取一个健康的连接
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      //利用这个健康的连接创建了HttpCodec对象
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        //让当前的流持有HttpCodec对象
        codec = resultCodec;
        //返回HttpCodec对象
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

这个方法主要工作:获取一个健康的连接,利用这个连接创建了HttpCodec对象,再将它返回。那么findHealthyConnection()这个方法又做了什么?

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
       //通过循环来获取连接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // 如果这个连接是新创建的,那他肯定是健康的,直接返回。
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      //如果不是新创建的,要检查是否健康.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      //不健康就释放当前流的资源,包括它承载的连接。再跳出循环继续寻找下一个连接
      //对于这个不健康的连接如果它是空闲的就将他从连接池移除
        noNewStreams();
        continue;
      }
     //健康就返回
      return candidate;
    }
  }

这个方法就是再不停的循环获取连接直到它获取到了一个健康的连接就将它返回,什么样的连接时不健康的呢?简单说,如果这个连接被关闭了那么它就不健康。

/** Returns true if this connection is ready to host new streams. */
  public boolean isHealthy(boolean doExtensiveChecks) {
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
      return false;
    }
    
    if (http2Connection != null) {
      return !http2Connection.isShutdown();
    }
   //...省略其他代码
    return true;
  }

那么获取连接的findConnection方法又做了什么呢?
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;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // 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 streams.
      releasedConnection = this.connection;
      //如果当前流持有一个连接,但是这个连接不能承载新流就释放这个连接返回连接内部的Socket
      //连接被释放了对于的Socket自然要关闭,toClose在后面被关闭。
      toClose = releaseIfNoNewStreams();
      //1、如果存在就尝试复用
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        result = this.connection;//复用存在的连接
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }
      //2、如果不存在就尝试从连接池获取一个
      if (result == null) {
        // Attempt to get a connection from the pool.
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    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();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");
      //2、走到这前面肯定没获取到连接,切换路由尝试再次从连接池获取
      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.
        List routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      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.
        route = selectedRoute;
        refusedStreamCount = 0;
        //3、前面都没有获取到连接就创建一个
        result = new RealConnection(connectionPool, selectedRoute);
        //将创建的新连接分配给流。其实就是让当前的StreamAllocation对象持有连接,并将流加到连接  
        //的引用集合中(通过这个集合可以判定连接被多少流引用了)。
        acquire(result, false);
      }
    }

    // 第二次从连接池获取到连接foundPooledConnection就会被赋值true,此时返回连接池获取的连接
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // 对于新创建的连接,要执行TCP + TLS握手来建立连接。
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // 将新创建的连接放到连接池中
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      /*如果当前持有的连接是一个多路复用连接,就从连接池中找找,看连接池中有没有可以到达相同
	*address的多路复用连接,如果有就释放当前持有的连接,去持有连接池获取的那个。
	*/
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    //关闭被释放连接的socket
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }

findConnection这个方法大致流程就是这样。
什么是多路复用连接?注释讲解的很清楚

/**
   * Returns true if this is an HTTP/2 connection. Such connections can be used in multiple HTTP
   * requests simultaneously.
   */
  public boolean isMultiplexed() {
    return http2Connection != null;
  }

如果是HTTP/2连接,则返回true。这样的连接可以同时用于多个HTTP请求。

你可能感兴趣的:(Android源码相关)