OkHttp源码解析(五)——cache缓存

时间:2021-12-23 08:10:36

1.从HttpEngine#sendReques说起

在这个代码当中,有下面这一段代码。

InternalCache responseCache = Internal.instance.internalCache(client);

额,上面的代码还是调用OkHttpCLient的静态代码块中的internalCache方法。

      @Override public InternalCache internalCache(OkHttpClient client) {
        return client.internalCache();
      }

我们继续看internalCache方法。

  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

我们这里需要看下InternalCache的实现,这个位于Cache.java里面。

2.InternalCache

  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) throws IOException {
      Cache.this.update(cached, network);
    }

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

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

就是一些对cache的控制,了sendRequest方法中的responseCache就是Cache中的InternalCache,因此我们继续看下面的代码。

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

这里调用get去获取缓存中的Response。

3.获取缓存

相关的方法位于Cache.java中。

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

上面的方法解释如下:

  • 获取请求url的md5值
  • 通过key获取DiskLruCache的Snapshot对象
  • new Entry
  • 生成Response对象
  • 判断request和response是否匹配(地址,请求方式、请求头)
    • 不匹配 返回null
    • 匹配 返回response

这样,如果缓存中有的话cacheCandidate就是缓存的结果。继续看sendRequest的代码。

long now = System.currentTimeMillis();
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    networkRequest = cacheStrategy.networkRequest;
    cacheResponse = cacheStrategy.cacheResponse;

在CacheStrategy.Factory的构造函数中,更改如下(这里就不贴相关的代码了)

  • 当前事前
  • 请求
  • chacheResponse
  • 发送请求的时间
  • 收到响应的时间
  • 服务日期 Date
  • 协议 Expires
  • 上一次修改时间 Last-Modified
  • Etag
  • Age 到现在为止的时间

好吧,我们还得继续看sendRequest方法。

    if (responseCache != null) {
      responseCache.trackResponse(cacheStrategy);
    }

如果缓存不为null,就掉用Cache的trackResponse方法。

4.Cache的trackRespons

  private synchronized void trackResponse(CacheStrategy cacheStrategy) {
    requestCount++;

    if (cacheStrategy.networkRequest != null) {
      // If this is a conditional request, we'll increment hitCount if/when it hits.
      networkCount++;
    } else if (cacheStrategy.cacheResponse != null) {
      // This response uses the cache and not the network. That's a cache hit.
      hitCount++;
    }
  }

上面方法解释如下:

  • 请求次数+1
  • 如果网络请求不是null +1
  • 如果缓存Response不为null +1

接下来我们就需要看下readRespons啦。

5.HttpEngine#readResponse

    if (cacheResponse != null) {
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, userResponse);
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }

如果有缓存的话,会根据条件去选择对应的策略,那么是什么条件呢。关键在于validate(cacheResponse, networkResponse)。我们看下这里的逻辑。核心判断如下。

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

这里的意思是我们控制的cache时间,如果没有超过cache的时间,就返回true,否则,返回false。如果在缓存的时间内,就启用缓存。那么,我们是在什么时候写入缓存的呢。

6.缓存的写入

。在readResponse的最下面,有如下代码。

    if (hasBody(userResponse)) { maybeCache(); userResponse = unzip(cacheWritingResponse(storeRequest, userResponse)); }

ps:如果能走到这里,说明,没有启用缓存。我们看下maybeCache方法,

  private void maybeCache() throws IOException {
    InternalCache responseCache = Internal.instance.internalCache(client);
    if (responseCache == null) return;

    // Should we cache this response for this request?
    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          responseCache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
      return;
    }

    // Offer this request to the cache.
    storeRequest = responseCache.put(userResponse);
  }
  • 判断是够可以缓存 不可以缓存的话讲对应的删除
  • 加入缓存

看下加入缓存的相关逻辑

  private CacheRequest put(Response response) throws IOException {
    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 (OkHeaders.hasVaryAll(response)) {
      return null;
    }

    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(urlToKey(response.request()));
      if (editor == null) {
        return null;
      }
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

额,貌似也没啥好看的。磁盘缓存 相应大家都懂得,

7.总结

Ohttp源码系列就暂时到这里把,当然还有许多东西没点到,在后面有需要的话会补下来的。