美文网首页
CacheInterceptor 解析

CacheInterceptor 解析

作者: taijielan | 来源:发表于2019-04-03 10:13 被阅读0次
如何用?

在OkHttpClient中配置缓存的文件

 Cache cache  = new Cache(new File("/sdcard/test/"),100*100*1024);
            OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache).build();
            Request request = new Request.Builder().url("http://www.imooc.com/courseimg/s/cover005_s.jpg").cacheControl(new CacheControl.Builder().noCache().build()).build();
            Call call = okHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Log.e("info","----onFailure");
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.e("info","----onResponse"+response.body().toString());
                }
            });

注意:noCache()表示不从缓存中获取Response,使用网络获取,noStore()表示服务器返回的Response不能保存,也不使用缓存。
通过Cache的构造方法我们可知okhttp的缓存是通过DiskLruCache实现。

 public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
  }

  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
    Log.i("wenfeng","new cache");
  }
分析

Okhttp 已经默认对请求有设置缓存拦截,在RealCall中 添加interceptors.add(new CacheInterceptor(client.internalCache()));

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

client.internalCache()中返回的是一个InternalCache接口,真正的存取,获取,更新,是通过桥接模式在Cache中实现。
下面分析下实现

 @Override public Response intercept(Chain chain) throws IOException {
    // 根据请求Request 来获取已有的缓存
      Response cacheCandidate = cache != null
          ? cache.get(chain.request())
          : null;
    //当前时间
      long now = System.currentTimeMillis();
    //生成一个缓存策略,用来判断是否使用网络缓存,或者本地缓存,或者都用
      CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //    1
      //如果网络请求为空,则不使用用网络请求
      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(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); // 2
      } 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.
      // 如果本地缓存存在,通过本地缓存和网络返回的响应组合构建一个reponse对象
      if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {
          //如果返回304 ,则从缓存中读取
          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); //3
          return response;
        } else {
          closeQuietly(cacheResponse.body());
        }
      }
      //如果本地缓存为空,则通过网络返回的响应构建一个response返回
      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);
      }

      return response;
    }

上面是这个缓存的处理过程

  • 整个缓存的流程是根据缓存策略,看是否使用request服务器端的response还是使用本地的缓存,如果进行网络请求,根据缓存策略,判断是否更新本地缓存。
  • 标注1 表示当前的缓存策略,如果 Request networkRequest不为null,表示进行网络请求,如果Response cacheResponse 不为null,表示进行使用本地缓存。
  • 标注2 表示将责任链(也就是网络请求)分发给下一个拦截器
  • 标注3 表示根据网络请求数据来更新本地缓存。
说下缓存策略也就是CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); 

调用的地方是:

 /**
     * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
     *通过使用缓存响应,来返回一种策略来满足请求,
     */
    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;
    }

看看CacheStrategy 生成的地方,getCandidate()方法,先了解几个概念:

  • “If-Modified-Since” :客户端发送给服务器的数据,用来验证当前请求的数据是不是有修改过,如果服务器的时间小于这个时间,表示服务器的时间没有修改过,客户端可以用本地的缓存数据,如果服务器的时间大于这个时间表示服务器的时间修改过,将会使用服务器的数据。
  • “If-None-Match”:需要和“ETag”配合,ETag表示资源的一种标识信息,用于标识某个资源,由服务端返回,优先级更高。客户端再次向服务器端请求的时候加入字段If-None-Match(和ETag的内容相同),服务端收到请求的该字段时(之前的Etag值),和资源的唯一标识进行对比,如果相同,说明没有改动,则返回状态码304,如果不同,说明资源被改过了,则返回状态码200和整个内容数据。
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.
      //如果本地缓存是"noStore"的,则本地缓存是不能使用的,
      // 简单的说如果本地缓存和requst 设置的是"noStore"的,则返回不带本地缓存的策略,因为"nostore"是不能使用本地缓存的,也不能将请求的数据缓存到本地。
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      //如果request设置不适用本地缓存或者request 有自己的请求条件时,则本地缓存是不能使用的。
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      //缓存存活时间
      long ageMillis = cacheResponseAge();
      //获取缓存响应里声明的新鲜时间,如果没有设置则为0
      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();
      //mustRevalidate 如果为true ,那么maxStaleSeconds 则不用验证了,因为已经过期了,所以没有无最大过期时间。如果缓存响应不是必须要再验证,并且请求有最大过期时间,则用请求的最大过期时间作为最大过期时间
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
      //如果支持缓存,缓存存活时间+最小新鲜时间<响应新鲜时间+响应过期后还可用的时间 则可以缓存
      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 {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      //在okhttp中实现
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      //构建一个新的request
      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      //构建缓存缓存策略
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

上面就是各种情况下,缓存策略的生成,其中涉及到一个CacheControl 这个类,这个类的主要作用是配置从服务器到客户端的缓存准则。简单的说就是控制哪些响应能被存储,哪些请求能使用这些存储的响应(也就是缓存),再简单点说就是哪些请求能使用本地缓存,以及客户端发出的请求,收到的响应,在何种情况下能本地缓存。

相关文章

网友评论

      本文标题:CacheInterceptor 解析

      本文链接:https://www.haomeiwen.com/subject/sfkabqtx.html