OkHttp源码之CacheInterceptor

CacheInterceptor也是okhttp自带的核心功能的拦截器,它负责处理http请求的缓存。今天我们来仔细分析下相关源码。

Http协议规定的缓存

在分析源码之前,我们需要事先对http协议的缓存有所了解,这里有篇文章写的很好 《http协议缓存》,大家最好看明白这篇文章再继续往下分析。
为了避免大家不想看其他文章,我简单的总结下:http缓存分为两种,一种强制缓存,一种对比缓存,强制缓存生效时直接使用以前的请求结果,无需发起网络请求。对比缓存生效时,无论怎样都会发起网络请求,如果请求结果未改变,服务端会返回304,但不会返回数据,数据从缓存中取,如果改变了会返回数据。具体详细规则还是得看上面提到的文章。

缓存结果获取

  @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
   //networkRequest 不为null,说明强制缓存规则不生效
    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) {
      //省略代码
    }
    // If we don't need the network, we're done.
    if (networkRequest == null) {
     //省略代码
    }
    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());
      }
    }
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code()==HTTP_NOT_MODIFIED) {
        //省略代码
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
//省略代码
    return response;
  }

在分析整体结构的时候,我们需要理解,这里的networkRequest如果不为null,则说明强制缓存生效,也就意味着这次请求直接使用上次的结果,无需发起真正的请求。而cacheResponse则是上次的缓存结果。整个缓存处理其实就是这两种情况的组合:

1.networkRequest == null && cacheResponse == null

    // 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();
    }

这种情况说明从缓存策略上来说强制缓存生效,应该直接取上次缓存结果,但由于未知原因缓存的结果没有了或者上次返回就没有,这里直接返回了失败的Response

2.networkRequest == null

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

这种情况下,强制缓存生效,按道理来说我们直接返回上次的cacheResponse就可以了,当然这里对Response的cacheResponse的body做了置空处理,不是什么大事。

3.networkRequest != null

强制缓存不生效,说明此时应该使用对比缓存,需要从服务端取数据:

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

按照对比缓存规则,取完之后应该判断是否返回304,如果是应该使用缓存结果:

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

可以看到确实是通过cacheResponse来构造Response的,而不是通过刚才请求的networkResponse来构造Response。当然,更新了一些必要数据而已。

4. 缓存失效

剩下的情况就是缓存完全失效了,这个时候应该利用刚才网络请求的结果:

 Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
//省略将这次请求结果加入缓存的代码
return response;

缓存策略判断

上面分析了缓存的使用方式,其实就是根据是否使用强制缓存(networkRequest为null),是否使用过对比缓存(networkRequest不为null,且cacheResponse不为null),那么具体使用哪种策略是怎么决定的呢,我们继续看源码:

@Override public Response intercept(Chain chain) throws IOException {
    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);
    }
   //省略代码
}

这段代码我们之前没有分析,这里有个CacheStrategy,就是由它来决定采用何种缓存规则的。okhttp为每一个请求都记录了一个Strategy。我们来看下这个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() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }
      // Drop the cached response if it's missing a required handshake.
      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.
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      CacheControl requestCaching = request.cacheControl();
     //省略代码
}

所谓的决定策略,其实就是决定是否要重新发起网络请求,上面的几种情况都是要发起请求的,包括:没有对应的缓存结果;https请求却没有握手信息;不允许缓存的请求(包括一些特殊状态码以及Header中明确禁止缓存)。
上述的几种情况其实都没有涉及到具体的缓存策略,没有通过Header中一些字段来判断,下面的几种情况就都是靠Header中的字段来决定的了,主要借助了一个类CacheControl,我们看下它是干什么的:

public final class CacheControl {
  private final boolean noCache;
  private final boolean noStore;
  private final int maxAgeSeconds;
  private final int sMaxAgeSeconds;
  private final boolean isPrivate;
  private final boolean isPublic;
  private final boolean mustRevalidate;
  private final int maxStaleSeconds;
  private final int minFreshSeconds;
  private final boolean onlyIfCached;
  private final boolean noTransform;
  private final boolean immutable;
}

我们看下成员变量,很明显,这里都是记录的http协议中的一些和缓存有关的字段,我们可以通过这些字段判断一个requeset是否允许缓存,一个Response采用何种缓存策略。
这个时候我们继续回到getCandidate()方法:

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

      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

可以看到这里分别获取了request的和Response的CacheControl,下面的代码就是按照标准的http缓存协议,从CacheControl中判断一些Header中的关键字决定缓存策略,这里就不再继续深入。

缓存的具体实现

上面讨论了是否使用缓存,什么时候采用何种策略,这节我们讨论下使用缓存时,缓存具体如何存的,如何获取的:

 @Override public Response intercept(Chain chain) throws IOException {
    //缓存的获取
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    //省略代码
 if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // 存入到缓存中去
        CacheRequest cacheRequest = cache.put(response); 
      }
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          //从缓存中删除
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

从上面可以看到,所有的缓存处理都是来自cache类:

final InternalCache cache;

最终传进来的是在OkHttpClient中设置的Cache类
先看下成员变量:

public final class Cache implements Closeable, Flushable {
  private static final int VERSION = 201105;
  private static final int ENTRY_METADATA = 0;
  private static final int ENTRY_BODY = 1;
  private static final int ENTRY_COUNT = 2;

  final InternalCache internalCache = new InternalCache() {
    @Override public Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public CacheRequest put(Response response) throws IOException {
      return Cache.this.put(response);
    }

    @Override public void remove(Request request) throws IOException {
      Cache.this.remove(request);
    }

    @Override public void update(Response cached, Response network) {
      Cache.this.update(cached, network);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };

  final DiskLruCache cache;

  /* read and write statistics, all guarded by 'this' */
  int writeSuccessCount;
  int writeAbortCount;
  private int networkCount;
  private int hitCount;
  private int requestCount;
}

从成员变量中可以猜测缓存应该是借助DiskLruCache实现的

缓存的put

@Nullable CacheRequest put(Response response) {
    String requestMethod = response.request().method();
    if (HttpMethod.invalidatesCache(response.request().method())) {
      try {
        remove(response.request());
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
      return null;
    }
    if (!requestMethod.equals("GET")) {
      // Don't cache non-GET responses. We're technically allowed to cache
      // HEAD requests and some POST requests, but the complexity of doing
      // so is high and the benefit is low.
      return null;
    }
    if (HttpHeaders.hasVaryAll(response)) {
      return null;
    }
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(key(response.request().url()));
      if (editor == null) {
        return null;
      }
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

总体逻辑很简单,首先做了个保险的判断,对方法是POST,PATCH,PUT,DELETE,MOVE的请求,将缓存清除掉,这些是不应该被缓存的。然后明确了一点,只有GET方法才会被缓存。
而真正的缓存写入到文件是通过一个叫Entry的辅助类来的,我们简单的看下writeTo()方法:

 public void writeTo(DiskLruCache.Editor editor) throws IOException {
      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));

      sink.writeUtf8(url)
          .writeByte('\n');
      sink.writeUtf8(requestMethod)
          .writeByte('\n');
      sink.writeDecimalLong(varyHeaders.size())
          .writeByte('\n');
    //省略类似代码

      
      sink.close();
    }

其实就是将请求信息按顺序写入到DiskLruCache中,最终由DiskLruCache写入到磁盘中。

缓存的get

@Nullable Response get(Request request) {
    String key = key(request.url());
    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;
  }

获取Response的结果没有任何复杂逻辑,先获取一个DiskLruCache.Snashot,然后构造一个Entry,最后借助这个Entry构造一个Response。至于Snashot是什么东西,我们下篇文章分析。

你可能感兴趣的:(OkHttp源码之CacheInterceptor)