OkHttp 源码学习(二)重试重定向、桥接、缓存拦截器

213 阅读11分钟

在上一篇文章OkHttp 源码学习(一) 请求与调度 中将 okhttp 请求封装与调用了解了一下,那么这篇文章继续分析责任链中各个环节都做了哪些事情,

private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  // 重试 重定向 拦截器
  interceptors.add(retryAndFollowUpInterceptor);
  // 桥接拦截器 主要用于 header 与 body 的封装
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  // 缓存拦截器
  interceptors.add(new CacheInterceptor(client.internalCache()));
  // 链接拦截器  okhttp 的精髓
  interceptors.add(new ConnectInterceptor(client));
  if (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  // 交互拦截器
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

在这个责任链中主要分为5个重要的模块,这5个模块各司其职,下面一个一个来分析

1.RetryAndFollowUpInterceptor 重试 重定向拦截器

这个拦截器的工作主要分为2个部分,其中一个非常重要的工作就是重试,具体逻辑如下,这里只将重试的代码保留

while (true) {
  if (canceled) {
    streamAllocation.release();
    throw new IOException("Canceled");
  }

  Response response = null;
  boolean releaseConnection = true;
  try {
    response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
    releaseConnection = false;
  } catch (RouteException e) {
    // The attempt to connect via a route failed. The request will not have been sent.
    if (!recover(e.getLastConnectException(), true, request)) 
        throw  e.getLastConnectException();
    releaseConnection = false;
    continue;
  } catch (IOException e) {
    // An attempt to communicate with a server failed. The request may have been sent.
    if (!recover(e, false, request)) throw e;
    releaseConnection = false;
    continue;
  } finally {
    // We're throwing an unchecked exception. Release any resources.
    if (releaseConnection) {
      streamAllocation.streamFailed(null);
      streamAllocation.release();
    }
  }
}

从上面的代码中可以看到 如果发生了 RouteException 与 IOException , okhttp 会不限次数的帮我们进行重试,下面来看一下具体的逻辑


/**
 * Report and attempt to recover from a failure to communicate with a server. Returns true if
 * {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
 * be recovered if the body is buffered.
 */
private boolean recover(IOException e, boolean routeException, Request userRequest) {
  streamAllocation.streamFailed(e);

  // The application layer has forbidden retries.
  if (!client.retryOnConnectionFailure()) return false;

  // We can't send the request body again.
  if (!routeException && userRequest.body() instanceof UnrepeatableRequestBody) return false;

  // This exception is fatal.
  if (!isRecoverable(e, routeException)) return false;

  // No more routes to attempt.
  if (!streamAllocation.hasMoreRoutes()) return false;

  // For failure recovery, use the same route selector with a new connection.
  return true;
}

看到这里主要分为4步拦截

if (! client.retryOnConnectionFailure()) return false;

这里的 client.retryOnConnectionFailure() 我们可以在okhttpclient创建时修改 方式如下

var client=OkHttpClient.Builder()
    .connectTimeout(5*1000,TimeUnit.SECONDS)
    .retryOnConnectionFailure(false)
    .addInterceptor {
            chain -> chain?.proceed(chain.request())
    }
    .addNetworkInterceptor {
        it.proceed(it.request())
    }
    .build()

这个值默认是true

if (!routeException && userRequest.body() instanceof UnrepeatableRequestBody) return false;

本次异常不是由路由引起的,并且 请求体 是 UnrepeatableRequestBody(不可重复的) 的,那就本次请求失败,不可以重试

if (!isRecoverable(e, routeException)) return false;

第三步又细分了很多种情况

private boolean isRecoverable(IOException e, boolean routeException) {
  // 协议异常,通常是服务器返回的body 与 header 的信息不匹配造成,
  if (e instanceof ProtocolException) {
    return false;
  }

  //打断异常,如果本次请求被打断,则不能重试
  if (e instanceof InterruptedIOException) {
    return e instanceof SocketTimeoutException && routeException;
  }

  // ssl 协议异常,并且是证书异常
  if (e instanceof SSLHandshakeException) {
    if (e.getCause() instanceof CertificateException) {
      return false;
    }
  }
  // ssl 认证异常 
  if (e instanceof SSLPeerUnverifiedException) {
    return false;
  }

  return true;
}
if (!streamAllocation.hasMoreRoutes()) return false;

从连接池取不到链接的时候,不能重试 ,

其实在我这里看得okhttp的代码是 4.0以下的版本,在4.0以上的版本还判断了如果当前请求是向服务器写一个文件,但是出现了 FileNodeFound 的异常也会让请求不能重试,这个也理解起来也比较简单,上传的文件没找到,本次请求没有意义了

到了这里重试就讲完了,下面继续来说重定向,还是上面的代码,只不过这次只保留重定向的代码

while (true) {
  Request followUp = followUpRequest(response);

  if (followUp == null) {
    if (!forWebSocket) {
      streamAllocation.release();
    }
    return response;
  }

  closeQuietly(response.body());

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

  if (followUp.body() instanceof UnrepeatableRequestBody) {
    throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
  }

  if (!sameConnection(response, followUp.url())) {
    streamAllocation.release();
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(followUp.url()));
  } else if (streamAllocation.stream() != null) {
    throw new IllegalStateException("Closing the body of " + response
        + " didn't close its backing stream. Bad interceptor?");
  }

  request = followUp;
  priorResponse = response;
}

这里的逻辑大致如下 1:这里的代码逻辑就是通过 followUpRequest 方法判断是否可以重新构建一个Request ,如果不能构建,则代表不需要重定向 ,本次请求完成,

2:如果重定向次数超过最大次数,则抛出异常 ,MAX_FOLLOW_UPS 为20,也就是超过20则抛出异常结束

3:拿到的body 只能单次使用,抛出异常

4:不是同一个请求 , 则重新创建请求,不是同一个请求的标准如下

private boolean sameConnection(Response response, HttpUrl followUp) {
  HttpUrl url = response.request().url();
  return url.host().equals(followUp.host())
      && url.port() == followUp.port()
      && url.scheme().equals(followUp.scheme());
}

BridgeInterceptor 桥接拦截器

桥接拦截器主要是处理 header 的中的 conent-length cookie 等信息,

@Override public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();

  RequestBody body = userRequest.body();
  if (body != null) {
    MediaType contentType = body.contentType();
    if (contentType != null) {
      requestBuilder.header("Content-Type", contentType.toString());
    }

    long contentLength = body.contentLength();
    if (contentLength != -1) {
      requestBuilder.header("Content-Length", Long.toString(contentLength));
      requestBuilder.removeHeader("Transfer-Encoding");
    } else {
      requestBuilder.header("Transfer-Encoding", "chunked");
      requestBuilder.removeHeader("Content-Length");
    }
  }

  if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
  }

  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  boolean transparentGzip = false;
  if (userRequest.header("Accept-Encoding") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
  }

  List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
  if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
  }

  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
  }

  Response networkResponse = chain.proceed(requestBuilder.build());

  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  Response.Builder responseBuilder = networkResponse.newBuilder()
      .request(userRequest);

  if (transparentGzip
      && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
      && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
        .removeAll("Content-Encoding")
        .removeAll("Content-Length")
        .build();
    responseBuilder.headers(strippedHeaders);
    responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}

这里需要注意的有2点

1:cookieJar 这个cookieJar 是BridgeInterceptor在创建之初由 okhttpclient 传入进来的,我们可以在创建okhttpclient 时自定义

var client=OkHttpClient.Builder()
    .connectTimeout(5*1000,TimeUnit.SECONDS)
    .retryOnConnectionFailure(false)
    .addInterceptor {
            chain -> chain?.proceed(chain.request())
    }
    .cookieJar()//TODO 这里设置 
    .addNetworkInterceptor {
        it.proceed(it.request())
    }
    .build()

2: userRequest.header("Accept-Encoding") == null 时自动为我们添加 gzip 请求,同时使用 GzipSource 将返回结果包装,

CacheInterceptor 缓存拦截器

开启okhttp 的缓存也比较简单,只需要在 初始化client 时 执行Cache 即可,代码如下

var client=OkHttpClient.Builder()
    .connectTimeout(5*1000,TimeUnit.SECONDS)
    .retryOnConnectionFailure(false)
    .addInterceptor {
            chain -> chain?.proceed(chain.request())
    }
    .cookieJar(object :CookieJar{
        override fun saveFromResponse(url: HttpUrl?, cookies: MutableList<Cookie>?) {
        }

        override fun loadForRequest(url: HttpUrl?): MutableList<Cookie> {
            return mutableListOf()
        }
    })
    // 开启缓存
    .cache(Cache(cachePath,20*1024*1024))
    .addNetworkInterceptor {
        it.proceed(it.request())
    }
    .build()

在加入了缓存的情况下,其实我们要做的请求就2件事, 1:在请求前拿缓存,如果缓存可以使用,则使用缓存,

// 注释1:先从缓存中拿
Response cacheCandidate = cache != null
    ? cache.get(chain.request())
    : null;

long now = System.currentTimeMillis();

//注释2:创建中间调度体,封装逻辑 
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

if (cache != null) {
  cache.trackResponse(strategy);
}

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.
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(EMPTY_BODY)
      .sentRequestAtMillis(-1L)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
}

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

虽然从逻辑上来说这个过程比较简单,但是okhttp 的实际处理是非常的复杂的 我们先从注释代码1说起,拿缓存

Response get(Request request) {
  String key = urlToKey(request);
  DiskLruCache.Snapshot snapshot;
  Entry entry;
  try {
    snapshot = cache.get(key);
    if (snapshot == null) {
      return null;
    }
  } catch (IOException e) {
    // Give up because the cache cannot be read.
    return null;
  }

  try {
    entry = new Entry(snapshot.getSource(ENTRY_METADATA));
  } catch (IOException e) {
    Util.closeQuietly(snapshot);
    return null;
  }

  Response response = entry.response(snapshot);

  if (!entry.matches(request, response)) {
    Util.closeQuietly(response.body());
    return null;
  }

  return response;
}

在拿缓存的大致过程如下, 1:先去 DiskLruCache 文件缓存中取快照 2:如果快存在通过快照信息的resouse[0]创建 Entry ,其中 resouse[0] 是按照 okhttp 的规则顺序的写入了一些数据 , 3:使用 entry 解析快照信息获取缓存请求

下面继续分析注释2代码

//注释2:创建中间调度体,封装逻辑 
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
public Factory(long nowMillis, Request request, Response cacheResponse) {
  this.nowMillis = nowMillis;
  this.request = request;
  this.cacheResponse = cacheResponse;

  if (cacheResponse != null) {
    this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
    this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
    Headers headers = cacheResponse.headers();
    for (int i = 0, size = headers.size(); i < size; i++) {
      String fieldName = headers.name(i);
      String value = headers.value(i);
      if ("Date".equalsIgnoreCase(fieldName)) {
        servedDate = HttpDate.parse(value);
        servedDateString = value;
      } else if ("Expires".equalsIgnoreCase(fieldName)) {
        expires = HttpDate.parse(value);
      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
        lastModified = HttpDate.parse(value);
        lastModifiedString = value;
      } else if ("ETag".equalsIgnoreCase(fieldName)) {
        etag = value;
      } else if ("Age".equalsIgnoreCase(fieldName)) {
        ageSeconds = HttpHeaders.parseSeconds(value, -1);
      }
    }
  }
}

注意这里我们传入的信息分别是 1:时间 2:请求 3:缓存 在创建过程中将所有数据缓存,同时解析缓存请求,从他的header 中获取 Date Expires Last-Modified ETag Age 等信息,并进行缓存

CacheStrategy.Factory.get 方法如下

public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();

  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    return new CacheStrategy(null, null);
  }

  return candidate;
}

这里的主要逻辑还是需要看 getCandidate 这个方法,代码如下

private CacheStrategy getCandidate() {
    //逻辑1
  if (cacheResponse == null) {
    return new CacheStrategy(request, null);
  }
    
    // 逻辑2
  if (request.isHttps() && cacheResponse.handshake() == null) {
    return new CacheStrategy(request, null);
  }

  // If this response shouldn't have been stored, it should never be used
  // as a response source. This check should be redundant as long as the
  // persistence store is well-behaved and the rules are constant.
  //逻辑3
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }

  CacheControl requestCaching = request.cacheControl();
  // 逻辑4
  if (requestCaching.noCache() || hasConditions(request)) {
    return new CacheStrategy(request, null);
  }

  long ageMillis = cacheResponseAge();
  long freshMillis = computeFreshnessLifetime();

  if (requestCaching.maxAgeSeconds() != -1) {
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

  long minFreshMillis = 0;
  if (requestCaching.minFreshSeconds() != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
  }

  long maxStaleMillis = 0;
  CacheControl responseCaching = cacheResponse.cacheControl();
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }
   
   // 逻辑5
  if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
    Response.Builder builder = cacheResponse.newBuilder();
    if (ageMillis + minFreshMillis >= freshMillis) {
      builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
    }
    long oneDayMillis = 24 * 60 * 60 * 1000L;
    if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
      builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
    }
    return new CacheStrategy(null, builder.build());
  }
  
  // Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
  conditionName = "If-None-Match";
  conditionValue = etag;
} else if (lastModified != null) {
  conditionName = "If-Modified-Since";
  conditionValue = lastModifiedString;
} else if (servedDate != null) {
  conditionName = "If-Modified-Since";
  conditionValue = servedDateString;
} else {
// 逻辑6
  return new CacheStrategy(request, null); // No condition! Make a regular request.
}

Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

//逻辑7
Request conditionalRequest = request.newBuilder()
    .headers(conditionalRequestHeaders.build())
    .build();
return new CacheStrategy(conditionalRequest, cacheResponse);

这里的逻辑如下 1: 如果没有缓存则使用 请求 request 构建 CacheStrategy 2: 如果是https请求,并且缓存中没有握手记录 则使用 请求 request 构建 CacheStrategy

3: isCacheable 判断

public static boolean isCacheable(Response response, Request request) {
  // Always go to network for uncacheable response codes (RFC 7231 section 6.1),
  // This implementation doesn't support caching partial content.
  switch (response.code()) {
    case HTTP_OK:
    case HTTP_NOT_AUTHORITATIVE:
    case HTTP_NO_CONTENT:
    case HTTP_MULT_CHOICE:
    case HTTP_MOVED_PERM:
    case HTTP_NOT_FOUND:
    case HTTP_BAD_METHOD:
    case HTTP_GONE:
    case HTTP_REQ_TOO_LONG:
    case HTTP_NOT_IMPLEMENTED:
    case StatusLine.HTTP_PERM_REDIRECT:
      // These codes can be cached unless headers forbid it.
      break;

    case HTTP_MOVED_TEMP:
    case StatusLine.HTTP_TEMP_REDIRECT:
      // These codes can only be cached with the right response headers.
      // http://tools.ietf.org/html/rfc7234#section-3
      // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
      if (response.header("Expires") != null
          || response.cacheControl().maxAgeSeconds() != -1
          || response.cacheControl().isPublic()
          || response.cacheControl().isPrivate()) {
        break;
      }
      // Fall-through.

    default:
      // All other codes cannot be cached.
      return false;
  }

  // A 'no-store' directive on request or response prevents the response from being cached.
  return !response.cacheControl().noStore() && !request.cacheControl().noStore();
}

这里的代码逻辑就是只有 HTTP_OK HTTP_NOT_AUTHORITATIVE HTTP_NO_CONTENT 等请求会查看 !response.cacheControl().noStore() && !request.cacheControl().noStore() 这个判断,其他都是false 同时这个 noStore 的意思就是请求或者相应的请求头中如果存在这个 Cache-Control ,并且 value中存在 noStore,那么本次不使用缓存

public CacheControl cacheControl() {
  CacheControl result = cacheControl;
  return result != null ? result : (cacheControl = CacheControl.parse(headers));
}



public static CacheControl parse(Headers headers) {
  boolean noCache = false;
  boolean noStore = false;
  int maxAgeSeconds = -1;
  int sMaxAgeSeconds = -1;
  boolean isPrivate = false;
  boolean isPublic = false;
  boolean mustRevalidate = false;
  int maxStaleSeconds = -1;
  int minFreshSeconds = -1;
  boolean onlyIfCached = false;
  boolean noTransform = false;

  boolean canUseHeaderValue = true;
  String headerValue = null;

  for (int i = 0, size = headers.size(); i < size; i++) {
    String name = headers.name(i);
    String value = headers.value(i);

    if (name.equalsIgnoreCase("Cache-Control")) {
      if (headerValue != null) {
        // Multiple cache-control headers means we can't use the raw value.
        canUseHeaderValue = false;
      } else {
        headerValue = value;
      }
    } else if (name.equalsIgnoreCase("Pragma")) {
      // Might specify additional cache-control params. We invalidate just in case.
      canUseHeaderValue = false;
    } else {
      continue;
    }

    int pos = 0;
    while (pos < value.length()) {
      int tokenStart = pos;
      pos = HttpHeaders.skipUntil(value, pos, "=,;");
      String directive = value.substring(tokenStart, pos).trim();
      String parameter;

      if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
        pos++; // consume ',' or ';' (if necessary)
        parameter = null;
      } else {
        pos++; // consume '='
        pos = HttpHeaders.skipWhitespace(value, pos);

        // quoted string
        if (pos < value.length() && value.charAt(pos) == '"') {
          pos++; // consume '"' open quote
          int parameterStart = pos;
          pos = HttpHeaders.skipUntil(value, pos, """);
          parameter = value.substring(parameterStart, pos);
          pos++; // consume '"' close quote (if necessary)

          // unquoted string
        } else {
          int parameterStart = pos;
          pos = HttpHeaders.skipUntil(value, pos, ",;");
          parameter = value.substring(parameterStart, pos).trim();
        }
      }

      if ("no-cache".equalsIgnoreCase(directive)) {
        noCache = true;
      } else if ("no-store".equalsIgnoreCase(directive)) {
        noStore = true;
      } else if ("max-age".equalsIgnoreCase(directive)) {
        maxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
      } else if ("s-maxage".equalsIgnoreCase(directive)) {
        sMaxAgeSeconds = HttpHeaders.parseSeconds(parameter, -1);
      } else if ("private".equalsIgnoreCase(directive)) {
        isPrivate = true;
      } else if ("public".equalsIgnoreCase(directive)) {
        isPublic = true;
      } else if ("must-revalidate".equalsIgnoreCase(directive)) {
        mustRevalidate = true;
      } else if ("max-stale".equalsIgnoreCase(directive)) {
        maxStaleSeconds = HttpHeaders.parseSeconds(parameter, Integer.MAX_VALUE);
      } else if ("min-fresh".equalsIgnoreCase(directive)) {
        minFreshSeconds = HttpHeaders.parseSeconds(parameter, -1);
      } else if ("only-if-cached".equalsIgnoreCase(directive)) {
        onlyIfCached = true;
      } else if ("no-transform".equalsIgnoreCase(directive)) {
        noTransform = true;
      }
    }
  }

上面是请求的 CacheControl 的获取过程,首次使用的是 parese Header 创建的,

4:服务器返回不能缓存,获取request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;

5:通过缓存的请求获取缓存请求的请求时间与数据的有效期,如果在有效期内,并且可以保存 则使用缓存构建 CacheStrategy

6: 如果在缓存中 eTag 等值均为null,那么则使用 请求构建 CacheStrategy

7: 根据标记信息,重新组装Header ,使用新的request 与 cache 构建 CacheStrategy

看完了组装 CacheStrategy 的过程在看他的使用缓存的逻辑就非常的简单里了

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

long now = System.currentTimeMillis();

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

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

//注释2
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(EMPTY_BODY)
      .sentRequestAtMillis(-1L)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
}

// 注释3
if (networkRequest == null) {
  return cacheResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .build();
}

1:如果缓从缓存组装的 cacheCandidate 存在,但是根据缓存的结果不存在,将 cacheCandidate 流信息等关闭

2:如果请求和缓存都不存在返回空body

3: 如果请求不存在,使用缓存构建结果,并返回

下面再来说说做完网络请求后重新缓存的过程

// 注释1 
if (cacheResponse != null) {
  if (validate(cacheResponse, networkResponse)) {
    Response response = cacheResponse.newBuilder()
        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
        .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());
  }
}
//注释2 
Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();

if (HttpHeaders.hasBody(response)) {
  CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
  response = cacheWritingResponse(cacheRequest, response);
}

1: 如果我们在请求后得到的 response 与前面缓存的 response 的 Last-Modified 有差异,则需要更新缓存,判断逻辑如下

private static boolean validate(Response cached, Response network) {
  if (network.code() == HTTP_NOT_MODIFIED) return true;

  // The HTTP spec says that if the network's response is older than our
  // cached response, we may return the cache's response. Like Chrome (but
  // unlike Firefox), this client prefers to return the newer response.
  Date lastModified = cached.headers().getDate("Last-Modified");
  if (lastModified != null) {
    Date networkLastModified = network.headers().getDate("Last-Modified");
    if (networkLastModified != null
        && networkLastModified.getTime() < lastModified.getTime()) {
      return true;
    }
  }

  return false;
}

2: 这个里面的逻辑就就是查看是否可以缓存,并缓缓

由于时间的关系这篇文章就到这里了,而且链接拦截器也是 okhttp 的精华所在,并不是简单几句话就能说明白的,而且在写这篇博客前,也没有想到能写这么长,不过为了不留死角,每一个关键的逻辑点我都相应的说明,面对现在的这个行情只能不停的充实自己才能让自己不至于被淘汰

加油吧!少年