Android深入理解源码——OkHttp篇(下)

声明:原创作品,转载请注明出处https://www.jianshu.com/p/8c32e928613c

这篇文章接着上面一篇文章来详细分析各个拦截器的实现机制,主要的拦截器有这几个:

  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor

接下来挨个看下:

1.RetryAndFollowUpInterceptor

这个拦截器是用来处理异常请求重试和重定向的,所谓重定向,说的简单点就是请求某一个资源,被告知资源被更改,让你换个路径重新请求。接下来就来看下源码是怎么实现的:


  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }

源码有点长,我们来挨个看下,首先拿到请求体request、这个链条chain以及transmitter,这个transmitter其实是应用层和网络层的一个桥梁。接下来会进入到一个while循环中,在循环一开始会调用transmitter的prepareToConnect方法进行网络层的初始化,如下代码,然后判断下该请求是否已被取消,如果是则直接抛出异常。

transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
  throw new IOException("Canceled");
}

接下来就是调用chain的proceed方法将request传递给下一个拦截器进行网络请求,如下:

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

这个proceed方法在一个try/catch中执行,当出现对应的异常时,会调用recover方法来判断这个请求是否是可恢复的,如果可恢复则会重新执行while循环进行请求重试。如果不可恢复则直接抛出对应的异常。
我们来看下recover的判断逻辑:

  private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // 应用层禁止重试
    if (!client.retryOnConnectionFailure()) return false;

    // 无法再次发送请求体
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // 发生严重的错误异常
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 没有额外的路由可尝试
    if (!transmitter.canRetry()) return false;

    return true;
  }

首先会判断下我们的client是否配置了当连接失败可以重试,如果没有则返回false,即不可恢复。如果我们配置了可以重试,那么接下来会判断我们的请求是否已经发送出去,并且请求只能被发送一次,如果满足条件则表示不可恢复。如果不满足条件,则会调用isRecoverable方法进行接下来的判断。这个方法会判断抛出的异常是什么异常,如果是协议异常或者其他的一些特殊的异常则不可恢复。否则就调用transmitter的canRetry()方法进行判断,这个方法内部会判断是否有更多的路由可重试,如果没有则返回false不可重试,如果上面的条件都不满足则返回true,表示可重试。
接下来我们跳出recover方法,继续看接下来的代码。
如果上面的执行没有抛出异常,则会继续往下接着执行:

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

接下来会判断下priorResponse是否为空,这个priorResponse是保存着上次返回的response,如果不为空则会创建一个新的response,这个新的response将老的response和当前的response组合起来。这里我们是第一次执行,所以priorResponse为空,里面也就不会执行。接下来再看下面的代码:


      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }

      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;

首先会通过response得到exchange和route,exchange可以理解成实际处理io的类。然后调用followUpRequest方法并传入exchange和route参数获取重定向后的request,当然如果不是重定向的话就会返回空。接下来,如果上面返回的followUp重定向request为空的话,则表示我们的请求是正常的,就直接返回。这样到这逻辑就执行结束了。如果不为空就会接着往下执行,如果followUp的body不为空并且只能被发送一次,那么就直接返回这个response,执行结束。当然这个isOneShot方法默认是false的,所以不会直接返回。接下来继续执行,会关闭一些资源,然后把上面的followUp重定向的request作为新的request,然后把重定向返回的response赋值给priorResponse,接着会重复while循环进行再次的网络请求。当然这里有个判断重定向次数的逻辑,如果重定向超出20次则会抛出异常。
这样我们的RetryAndFollowUpInterceptor拦截器就分析完了。

2.BridgeInterceptor

接下来看下BridgeInterceptor拦截器,这个拦截器顾名思义是起到一个桥梁的作用,连接应用层和网络层的代码。相当于把应用层的代码转换成比较繁琐的HTTP协议相关的东西,比如报文头部的一些字段。比较简单这里就不展开说了。

3.CacheInterceptor

接下来看下CacheInterceptor这个拦截器,看名字就可以看出来这个是用来缓冲的,缓存HTTP返回的数据。讲这个缓存拦截器之前还是有必要讲下HTTP的缓存机制。

HTTP缓存机制

我们知道一个HTTP请求,其实就是客户端发送请求报文,然后服务器接返回响应报文的过程。通常当我们需要某个资源的时候我们就会直接从服务器那请求,但如果每次请求时服务器资源都是一样的没有发生改变,这时我们就可以在第一次拿到资源后存在本地,下次如果需要就直接从本地读取。但是有个问题,什么时候从本地获取什么时候从服务器拉取。这就涉及到HTTP的缓存机制。
HTTP缓存机制听起来挺复杂,其实就是利用一些HTTP报文头来定义一套缓存规则。
HTTP缓存分强制缓存对比缓存

强制缓存


如上图所示,左右两个图分别是本地缓存命中和不命中的流程。当缓存命中,即缓存数据库中有缓存数据并且没有失效,就可以直接返回数据,不用向服务器发起请求。如果没有命中,即缓存数据库中没有缓存数据或者数据失效,那么就要向服务器发起请求,服务器成功返回后,将数据保存到数据库。那么上面提到的怎么确定缓存数据是否失效呢?
有两种方式,分别是用ExpiresCache-Control字段
Expires
这个比较简单,就是当向服务器请求资源时,服务器会在响应报文头部增加Expires字段,表示这个资源的到期时间,如果下次请求数据的时间在这个时间内就直接使用缓存数据,否则就要重新向服务器请求资源。不过这个字段是HTTP1.0的,现在浏览器默认使用HTTP1.1。
Cache-Control
由于Expires过期时间是服务器给的,可能会和客户端的时间不一致,从而导致误差的出现。所以引入了Cache-Control规则。Cache-Control定义很多字段:

字段 含义
private 客户端可以缓存
public 客户端和代理服务器都可缓存
max-age = xxx 缓存在xxx秒后失效
no-cache 需要使用对比缓存来验证数据
no-store 所有数据都不缓存

对比缓存


上面左右分别是缓存命中和不命中的情况,可以看到所谓对比缓存就是当向服务器请求资源时,服务器会同时给你一个数据标识,下次再请求的时候要带上这个标识,然后服务器会验证这个标识,如果验证到这个标识对应的数据未失效则返回304告知使用本地缓存数据,否则返回最新的资源以及新的数据标识。这个标识有点类似于APP的登录token,第一次登录时服务器会返回一个token,后续再登录只用发送这个token给服务器就可以。当然这里不叫token。有下面两种方式:Last-Modified/If-Modified-SinceEtag/If-None-Match。下面分别来看下这两种方式:
Last-Modified/If-Modified-Since
当客户端向服务器发起请求时,服务器返回响应报文的同时还会在报文头部添加该资源最近一次修改的时间,用Last-Modified来表示,后面跟具体时间,这样当客户端再次需要这个数据时,要在请求报文头部增加If-Modified-Since字段,内容就是之前Last-Modified后面的时间,服务器收到If-Modified-Since的值后,会进行校验看最近更改时间是否一致,如果一致则返回304状态码,告知客户端资源未更改可直接使用本地缓存,否则会返回新的资源和最近的更改时间。
Etag/If-None-Match
这个流程类似,当客户端向服务器发起请求时,服务器返回响应报文的同时会返回该资源的唯一标识Etag,有点类似token,生成规则由服务器决定。当客户端再次发起请求是需要在报文头部用If-None-Match字段后面就是上次保存的Etag,服务器接收到会校验这个值,如果资源更新了则这个值就会校验出错,那么就会直接返回新的数据和新的Etag,否则返回304告知客户端使用本地缓存。

缓存流程

上面我们看到,HTTP缓存有好几种方式,每种方式所用字段也不一样,那到底该使用哪种,或者说当同时出现上面的情况,以哪个为先,其实这也是由一定流程和优先级的。他们的优先级和流程图如下:


知道HTTP的缓存机制,再来看这个CacheInterceptor会容易很多,我们来看下,

    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

首先有个内部cache容器,如果cache不为空则获取当前request对应的response,否则返回空值。

    long now = System.currentTimeMillis();
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

上面代码由request和cacheCandidate定义了一个CacheStrategy类,CacheStrategy里具体实现其实就是我们上面讲的HTTP缓存机制,然后获取strategy的networkRequest和cacheResponse,这两个不一定都有值,有可能为空,接下来的代码就是根据这两个是否为空的情况来判断是要网络请求还是直接使用缓存数据库。

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

这句比较好理解,如果cacheCandidate不为空并且cacheResponse为空,就清空之前的缓存。

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

上面也比较好理解,如果不是用网络并且之前也没缓存,就返回504错误。

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

如果没有网路,但之前有缓存,则直接返回之前的缓存

    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

接下里,如果networkRequest不为空,则进行网络请求。

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

如果之前有缓存,并且上面的网络请求返回304,则使用之前的缓存,并更新cache缓存集合。

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }
    return response;

如果不是304则说明是新的资源,则接下里就是缓存这个新的response并返回。
这样CacheInterceptor这个拦截器就说完了。

4.ConnectInterceptor

接下来看下连接拦截器ConnectInterceptor,先看下它的intercept方法:

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    return realChain.proceed(request, transmitter, exchange);
  }

上面我们可以看到,这里的核心代码是通过transmitter的newExchange方法创建一个Exchange对象,然后把它传入到下一个拦截器中,这个Exchange可以理解成每次客户端向服务器请求时进行的数据交换,说白了就是后面的拦截器就是通过这个类来进行数据的读写操作,而这个拦截器做得工作就是与服务器建立连接,然后提供这个Exchange对象。所以接下来重点来看下这个对象是如何被创建出来的。我们进入newExchange方法:

  /** Returns a new exchange to carry a new request and response. */
  Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
      if (noMoreExchanges) throw new IllegalStateException("released");
      if (exchange != null) throw new IllegalStateException("exchange != null");
    }
    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;
    }
  }

这里关键是这两行代码:

    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

先是通过exchangeFinder的find方法获取一个ExchangeCodec对象,然后利用这个ExchangeCodec对象再创建Exchange对象。这里可能有人会感到奇怪,这里的exchangeFinder是哪来的,其实就在RetryAndFollowUpInterceptortransmitter.prepareToConnect(request);这行代码里就已经初始化好了,可以进入这个方法看下:

  public void prepareToConnect(Request request) {
    if (this.request != null) {
      if (sameConnection(this.request.url(), request.url())) return; // Already ready.
      if (exchange != null) throw new IllegalStateException();

      if (exchangeFinder != null) {
        maybeReleaseConnection(null, true);
        exchangeFinder = null;
      }
    }

    this.request = request;
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }

然后这里通过exchangeFinder找到一个ExchangeCodec,这个其实就是一个编码解码器,通俗点就是针对不同的协议比如HTTP1和HTTP2采用读写协议的不同。接下来就继续看下这个find方法是如何实现的:

  public ExchangeCodec find(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();
    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      return resultConnection.newCodec(client, chain);
    } catch (RouteException e) {
      trackFailure();
      throw e;
    } catch (IOException e) {
      trackFailure();
      throw new RouteException(e);
    }
  }

可以看到里面主要是调用findHealthyConnection这个方法获取一个客户端和服务器的连接,然后调用这个newCodec方法来创建ExchangeCodec,所以接下来就看下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);
      // 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循环,通过findConnection来找到一个连接,如果这个连接是一个新的连接就直接返回,否则还需要做下额外的检查,如果这个连接不是健康的连接,就标志这个连接为不可用并且再重新查找连接,这样不断循环直到找到可用的连接。这里继续往下看下findConnection是如何实现的:

  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.
        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.
        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.
    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;
  }

这个方法里的代码贼ji儿长,我们不要慌慢慢来分析下,这里我们先不看那些细枝末节,挑重点的来看,首先我们来看下这段代码:

      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;
      }

这里首先判断下当前transmitter内存中的连接是否可用,如果不可用就回收掉,如果可用的话直接赋值给result,然后后面就直接返回这个连接。当连接不可用的时候,就接着往下执行,主要代码如下:

      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;
        }
      }
    }

这里通过一个transmitterAcquirePooledConnection方法来获取一个连接,这个方法传入了一个transmitter参数,如果找到可用连接那么transmitter中的connection就是有值的,所以就将transmitter.connection赋值给result,接下来就看下这个方法:

  boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
      @Nullable List<Route> routes, boolean requireMultiplexed) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (requireMultiplexed && !connection.isMultiplexed()) continue;
      if (!connection.isEligible(address, routes)) continue;
      transmitter.acquireConnectionNoEvents(connection);
      return true;
    }
    return false;
  }

这个方法参数传入了Address连接地址,Transmitter传输协议层,Route路由列表,这个Route其实就比Address多了一个代理类型,最后一个参数是否要求多路复用,然后我们看方法里面具体代码,里面是一个对连接池的遍历,如果当前的连接不是多路复用,但如果requireMultiplexed是true即要求多路复用那就执行continue遍历下一个connection,这里我们传入的requireMultiplexed值为false,所以会接着执行下面的代码,也就是通过调用connection 的isEligible方法来判断当前的连接是否可用,如果不可用就接着遍历下个connection,否则就执行下面的代码获取这个连接。我们看下这个isEligible方法:

  boolean isEligible(Address address, @Nullable List<Route> routes) {
    // 如果一个连接已经有一个或多个请求或者该连接不可用就直接返回false
    if (transmitters.size() >= allocationLimit || noNewExchanges) return false;

    // 除了主机名外,如果当前连接的路由地址和要请求的地址不同就直接返回false
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    // 如果主机名也一样说明是同一个连接返回true,表示该连接可用
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
    // 如果域名不一样,说明连接不可重用,但是有一种情况除外,就是如果当前为HTTP2协议,域名不一样也是可以重用连接的,这个叫做连接合并,具体连接合并的概念可以参考一下文章
    // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
    // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

    // 1. 当前连接必须为HTTP2,否则为不可用
    if (http2Connection == null) return false;

    // 2. 不同的路由必须对应到同一台主机,否则为不可用
    if (routes == null || !routeMatchesAny(routes)) return false;

    // 3. 下面是验证证书相关的东西
    if (address.hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }

    return true; // 该连接可重用
  }

上面方法中的代码都做了注释,相信还是很好理解的。上面我们调用transmitterAcquirePooledConnection方法是传入的routes为null,表示只是在连接池中查找HTTP1非多路复用的连接。如果找不到,我们接着再看下面:

    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) {
        routes = routeSelection.getAll();
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        }
      }

      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        //创建一个连接(实际连接动作在后面)
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }

    // 如果找到则直接返回
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // 建立连接
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

当上面HTTP1的连接找不到时,我们当前请求可能有很多其他路由,比如有很多代理服务器,它们组成一个个IP列表,相当于有很多route,然后把这个route集合传入transmitterAcquirePooledConnection方法,来查找可多路复用的HTTP2连接。如果还没找到可用的连接就自己创建一个RealConnection然后调用connect方法建立连接。
建立完连接后我们接着看下:

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;

可以看到连接建立成功后,并不是马上返回,而是又调用了一次transmitterAcquirePooledConnection方法,并传入了routes且requireMultiplexed参数为true,说明此时是在连接池中只查找多路复用的,为啥还要查找一遍?不是连接已经创建成功了?因为假如当我们正好同时进行两个请求时,可能会出现创建了两次连接,但是如果这两个连接符合多路复用,那么就会造成资源浪费,所以每次建立连接后会再检查遍,确认连接池没有可用连接才返回当前连接。这样整个连接查找的过程的就分析完了。
接下来我们来简单看下result.connect方法是如何建立连接的:

if (route.requiresTunnel()) {
  connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
  if (rawSocket == null) {
    break;
  }
} else {
  connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);

主要看下上面的关键代码,可以看到,如果需要建立隧道tunnel则先建立tunnel,没有就直接创建socket连接即TCP连接,建立连接后通过establishProtocol方法来进行协议握手,比如HTTPS相关的SSL握手及HTTP2相关协议,这里就不展开讲了。
上面我们用大量的篇幅讲解了连接的获取和建立,知道这个流程其实对ConnectInterceptor这个拦截器就已经了解得差不多了,其他的一些细枝末节稍微再看下就好了。接下来来看下下一个拦截器:

5.CallServerInterceptor

 @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        if (request.body().isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

这是实际和服务端进行数据交互的拦截器,可以看到正如上面所说,它的数据交互就是用我们在ConnectInterceptor中创建的Exchange来进行数据的读写。如果你继续深挖下去的话其实可以看到这里数据的读写操作是用到了Square他们自己家的另一个开源库okio,这个库是专门处理I/O流的读写,比Java自带那一套API要方便很多,有兴趣的同学可以研究下这个库,这里就不继续展开了。

6.结尾

到这里OkHttp中的拦截器也就都分析完了,拦截器的处理流程也是OkHttp的最妙的地方,理解了其中拦截器的实现也算是对该库有了一个很好的理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章