CacheInterceptor

94 阅读5分钟

源码解析

@Override public Response intercept(Chain chain) throws IOException {
    //默认cache为null,可以配置cache,不为空尝试获取缓存中的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根据response,time,request创建一个缓存策略,用于判断怎样使用缓存
    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.
    //如果缓存策略中禁止使用网络,并且缓存又为空,则构建一个Resposne直接返回,注意返回码=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.
    //不使用网络,但是又缓存,直接返回缓存
    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());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    //当缓存响应和网络响应同时存在的时候,选择用哪个
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //如果返回码是304,客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户
        // 只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
        //则使用缓存的响应
        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();
    //所以默认创建的OkHttpClient是没有缓存的
    if (cache != null) {
      //将响应缓存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        //缓存Resposne的Header信息
        CacheRequest cacheRequest = cache.put(response);
        //缓存body
        return cacheWritingResponse(cacheRequest, response);
      }
      //只能缓存GET....不然移除request
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
  1. 没有缓存和禁止使用网络 直接返回504
  2. 有缓存禁止使用网络直接返回缓存
  3. 请求网络,如果后台内容没有改变,返回缓存
  4. 后台内容改变缓存后台返回的内容,并返回。

Cache.java

CacheRequest put(Response response) {
    String requestMethod = response.request().method();
    //判断请求如果是"POST"、"PATCH"、"PUT"、"DELETE"、"MOVE"中的任何一个则调用DiskLruCache.remove(urlToKey(request));将这个请求从缓存中移除出去。
    if (HttpMethod.invalidatesCache(response.request().method())) {
      try {
        remove(response.request());
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
      return null;
    }
    //判断请求如果不是Get则不进行缓存,直接返回null。官方给的解释是缓存get方法得到的Response效率高,其它方法的Response没有缓存效率低。通常通过get方法获取到的数据都是固定不变的的,因此缓存效率自然就高了。其它方法会根据请求报文参数的不同得到不同的Response,因此缓存效率自然而然就低了。
    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;
    }
     //判断请求中的http数据包中headers是否有符号"*"的通配符,有则不缓存直接返回null
    if (HttpHeaders.hasVaryAll(response)) {
      return null;
    }
    //由Response对象构建一个Entry对象,Entry是Cache的一个内部类
    Entry entry = new Entry(response);
    //通过调用DiskLruCache.edit();方法得到一个DiskLruCache.Editor对象。
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(key(response.request().url()));
      if (editor == null) {
        return null;
      }
      //把这个entry写入
      //方法内部是通过Okio.buffer(editor.newSink(ENTRY_METADATA));获取到一个BufferedSink对象,随后将Entry中存储的Http报头数据写入到sink流中。
      entry.writeTo(editor);
      //构建一个CacheRequestImpl对象,构造器中通过editor.newSink(ENTRY_BODY)方法获得Sink对象
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

通过DiskLruCache.Edito写入new的Entry。

remove

  void remove(Request request) throws IOException {
    cache.remove(key(request.url()));
  }
  public static String key(HttpUrl url) {
    return ByteString.encodeUtf8(url.toString()).md5().hex();
  }

update

void update(Response cached, Response network) {
    //用response构造一个Entry对象
    Entry entry = new Entry(network);
    //从命中缓存中获取到的DiskLruCache.Snapshot
    DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
    //从DiskLruCache.Snapshot获取DiskLruCache.Editor()对象
    DiskLruCache.Editor editor = null;
    try {
      editor = snapshot.edit(); // Returns null if snapshot is not current.
      if (editor != null) {
        //将entry写入editor中
        entry.writeTo(editor);
        editor.commit();
      }
    } catch (IOException e) {
      abortQuietly(editor);
    }
  }

get

 Response get(Request request) {
    //获取url经过MD5和HEX的key
    String key = key(request.url());
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
     //根据key来获取一个snapshot,由此可知我们的key-value里面的value对应的是snapshot
      snapshot = cache.get(key);
      if (snapshot == null) {
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }
    //利用前面的Snapshot创建一个Entry对象。存储的内容是响应的Http数据包Header部分的数据。snapshot.getSource得到的是一个Source对象 (source是okio里面的一个接口)
    try {
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }

    //利用entry和snapshot得到Response对象,该方法内部会利用前面的Entry和Snapshot得到响应的Http数据包Body(body的获取方式通过snapshot.getSource(ENTRY_BODY)得到)创建一个CacheResponseBody对象;再利用该CacheResponseBody对象和第三步得到的Entry对象构建一个Response的对象,这样该对象就包含了一个网络响应的全部数据了。
    Response response = entry.response(snapshot);
    //对request和Response进行比配检查,成功则返回该Response。匹配方法就是url.equals(request.url().toString()) && requestMethod.equals(request.method()) && OkHeaders.varyMatches(response, varyHeaders, request);其中Entry.url和Entry.requestMethod两个值在构建的时候就被初始化好了,初始化值从命中的缓存中获取。因此该匹配方法就是将缓存的请求url和请求方法跟新的客户请求进行对比。最后OkHeaders.varyMatches(response, varyHeaders, request)是检查命中的缓存Http报头跟新的客户请求的Http报头中的键值对是否一样。如果全部结果为真,则返回命中的Response。
    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }

参考: