okHttp攔截器分析(二)

image.png

繼續(xù)上一篇的okHttp攔截器分析(一),下一個要分析的攔截器是CacheInterceptor,聽名字就知道跟緩存有關(guān),在這之前,我們先來看看一張Http緩存的流程圖,網(wǎng)絡(luò)上找到的:


image.png

當(dāng)發(fā)送相同請求的時候,先判斷緩存是否過期,如果過期了,該請求會攜帶If-Modified-Since和If-None-Match,通過這兩個值,判斷本地資源是否發(fā)生變化,沒有變化直接獲取緩存并返回code 304。
注:
Last-Modified:服務(wù)器返回給客戶端的頭部信息,表示資源的最后修改時間(ETag 比較的是響應(yīng)內(nèi)容的特征值,而Last-Modified 比較的是響應(yīng)內(nèi)容的修改時間)。

很好,接下來我們來看看okHttp里面的緩存攔截器關(guān)鍵實現(xiàn):

/** Serves requests from the cache and writes responses to the cache. */
public final class CacheInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
    //獲取到緩存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    
    //這個類有點叼,request和緩存的response都是CacheStrategy類返回的,也許這個CacheStrategy就是管理者吧,決定到底使用緩存還是進行網(wǎng)絡(luò)請求
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
    
    //緩存不能使用,關(guān)閉
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    //  1- 如果無網(wǎng)絡(luò)訪問(請求體networkRequest為null,內(nèi)部的url也是null),又無緩存,返回504錯誤
    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();
    }

    // If we don't need the network, we're done.
    //   2 - 如果不需要網(wǎng)絡(luò)請求,直接返回緩存數(shù)據(jù)
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
     // 3 - 進行網(wǎng)絡(luò)請求,得到 網(wǎng)絡(luò)返回數(shù)據(jù)
      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());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //4 - HTTP_NOT_MODIFIED 標(biāo)識緩存有效,網(wǎng)絡(luò)請求返回數(shù)據(jù)和緩存數(shù)據(jù)合并,并更新緩存
    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());
      }
    }

    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);
        //5- 判斷有無緩存,寫入緩存
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

}

上面關(guān)鍵代碼,我們來總結(jié)下執(zhí)行流程:
1 如果無網(wǎng)絡(luò)訪問,又無緩存,返回504錯誤,執(zhí)行2
2 如果不需要網(wǎng)絡(luò)請求,直接返回緩存數(shù)據(jù),否則 執(zhí)行3
3 進行網(wǎng)絡(luò)請求,得到網(wǎng)絡(luò)返回數(shù)據(jù),執(zhí)行4
4 HTTP_NOT_MODIFIED 標(biāo)識緩存有效,網(wǎng)絡(luò)請求返回數(shù)據(jù)和緩存數(shù)據(jù)合并,并更新緩存,否則 執(zhí)行5
5 判斷有無緩存,寫入緩存并返回response


image.png

很好,CacheInterceptor分析完了,喝杯茶壓壓驚。

接下來 我們來看看ConnectInterceptor,字如其名,是一個跟連接有關(guān)的攔截器,我們看看關(guān)鍵代碼:

/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
   @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 = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

代碼看的去很少,我們只需要關(guān)注下面兩行:

HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

不管是httpCodec,還是connection,都是由streamAllocation完成,我們來看看newStream()里面做了什么:

public HttpCodec newStream(
      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);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

......
 public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

findHealthyConnection ,是找到一個可用連接的意思,重點就在這,通過找到的可用連接newCodec(),返回了HttpCodec的實現(xiàn)類Http1Codec對象。我們?nèi)タ纯磃indHealthyConnection(),看看如何找到可用連接:

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)) {//判斷連接是否可用
        noNewStreams();//連接不可用,移除
        continue;//不可用,就一直持續(xù)
      }
 
      return candidate;
    }
  }

哇,這個方法用了死循環(huán),如果找不到可用連接,就一直卡在這里,再來看看findConnection()關(guān)鍵方法,有點長,分析都在源碼里面的注釋上:

 /**
   * 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;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) { //
      //經(jīng)過releaseIfNoNewStreams,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;
      }
      
      //無可用連接,去連接池connectionPool中獲取
      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) {//上面通過去連接池中找,如果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");
      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.
      //提供address,再次從連接池中獲取連接
        List<Route> 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;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }

    // 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);
    routeDatabase().connected(result.route());

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

      // Pool the connection.
     //將新生成的連接放入連接池中
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      //如果是一個http2連接,http2連接應(yīng)具有多路復(fù)用特性,
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

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

我們來回顧下上面的獲取可用連接的流程:
先檢測鏈接是否可用:
a 可用的話直接返回,結(jié)束流程。
b 不可用,先去 連接池中查找:
連接池找到:結(jié)束流程。
未找到:提供address,再次去連接池中查找。如果找到了直接結(jié)束流程,如果沒找到:生成一個新的連接,并且將這個新的鏈接加入到連接池,然后返回這個新鏈接。

ConnectInterceptor 分析結(jié)束。

接下來是CallServerInterceptor,這個攔截器用來完成最終的請求執(zhí)行,這里面涉及到OKio這個庫,假裝它就是一個httpUrlConnection就行了,由于是另外一個庫,這里就不分析了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容