OKHttp源码分析(三)缓存

OKhttp的缓存实现在CacheInterceptor中,okhttp进行请求时,会调用构建拦截器链,并执行每个拦截器的intercept方法。OKhttp的缓存策略就在CacheInterceptor中的intercept方法实现,下面我们来看一看CacheInterceptor中的intercept方法。

 @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);
    }
//如果当前缓存不符合要求,将其close
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
// 如果不能使用网络,同时又没有符合条件的缓存,直接抛504错误
    // 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();
    }
 // 如果有缓存同时又不使用网络,则直接返回缓存结果
    // 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());
      }
    }
// 如果既有缓存,同时又发起了请求,说明此时是一个Conditional Get请求
    // If we have a cache response too, then we're doing a conditional get.
    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)) {
      // 将网络响应写入cache中
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

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

    return response;
  }

重要方法我已经注释,大家可以从代码中查看。

//首先尝试获取缓存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

这里的cache是传入的是final InternalCache cache;这个cache是一个接口,具体传入什么cache需要到cacheInterceptor的构造函数中去找。

 interceptors.add(new CacheInterceptor(client.internalCache()));

OkHttpClient.internalCache()的实现

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

这里的cache就不是接口cache了,而是Cache类Cache类中的构造函数中创建了缓存DiskLruCache。而DiskLruCache内部使用的是LinkedHashMap

Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
  int redundantOpCount;

接下来我们回到CacheInterceptor继续往下看,获取缓存策略

 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

几乎所有的动作都是以CacheStrategy缓存策略为依据做出的,那么接下来看下缓存策略是如何生成的,相关代码实现

 private CacheStrategy getCandidate() {
      // 没有缓存发起网络请求.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // 如果是https,但没有TLS握手发起网络请求.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
      //如果这条response没有被存储,那么它就无法作为response被使用,只要持久化良好并且规则不变,那么这条规则无用
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
//如果当前的缓存策略是不缓存或者是conditional get,发起网络请求
      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);
      }
 //ageMillis:缓存age
      long ageMillis = cacheResponseAge();
      //freshMillis:缓存保鲜时间
      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;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
 //如果 age + min-fresh >= max-age && age + min-fresh < max-age + max-stale,则虽然缓存过期了,但是缓存继续可以使用,只是在头部添加 110 警告码
      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.
      // 发起conditional get请求
      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 {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

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

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

这个cacheStrategy描述了缓存相关信息,具体操作详见上面代码注释。

总结:Okhttp的缓存在CacheInterceptor中实现,核心方法intercept,主要操作的cache是DiskLruCache,通过CacheStrategy来判断是从网络获取还是从缓存中获取,以及是否更改缓存内容。后面我将分析一下DiskLruCache。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值