美文网首页
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