Okhttp 缓存策略的获取

Okhttp 缓存策略的获取

  • 在缓存拦截器那我们提到过 第二步就是要获取一个缓存策略对象 CacheStrategy

CacheStrategy 是什么,有什么用

  • 直接上代码

    • /**
       * 给定一个请求和缓存响应,这将确定是使用网络、缓存还是两者都使用。
       */
      public final class CacheStrategy {
        /** 在网络发送请求如果该Call不使用网络请求 该值为null 代表使用网络请求*/
        public final @Nullable Request networkRequest;
      
        /**缓存的response 如果该Call 不适用缓存该值为null* 代表使用缓存/
        public final @Nullable Response cacheResponse;
      }
      
    • 总结

      + 是什么
      + 是一个内部维护一个Request 和一个Response 的类
      + 有什么用
      + 通过给内部的request 和response 的值,来确定该次请求是使用网络还是缓存,还是二者 都使用
      + 这里二者都用 就是对比网络和缓存的值 相同则返回缓存response 不同返回网络response

networkRequest,response 值是怎么生成的

  • 要想了解怎么生成首先我们得知道在哪里通过什么方法获取的CacheStrategy,明显的这里是在CacheInterceptor中获取

    • CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
      
    • 可以看到是通过CacheStrategy.Factory的get方法 下面看下Factory的代码

      • public static class Factory {
           final long nowMillis;
           final Request request;
           final Response cacheResponse;
          
           /** 表示是服务器是在什么时候响应回客户端的 (如果已知的话) */
           private Date servedDate;
           private String servedDateString;
          
           /** 缓存响应的最后修改日期(如果已知的话)。
            * lastModified(包含在response里)与If-Modified-Since(包含在request里)配合使用
            * 首次发送请求成功服务器会返回lastModified与If
            *  当再次发送该请求客户端会同时发送一个If-Modified-Since,而它的值就是lastModified的值
            *  服务器接收到request后会取出值进行对比 若相同则服务器资源未改变可以使用缓存
            *  */
           private Date lastModified;
           private String lastModifiedString;
          
           /**
            * 缓存响应的过期日期(如果已知的话)。
            * 如果同时设置了该字段和max age,则首选max age。
            */
           private Date expires;
          
           /**
            * 由OkHttp设置的扩展头,指定缓存的HTTP请求首次发起时的时间戳。
            */
           private long sentRequestMillis;
          
           /**
            * 由OkHttp设置的扩展头,指定第一次接收缓存的HTTP响应时的时间戳。
            */
           private long receivedResponseMillis;
          
           /**
            * 服务器计算的出的一串值 用来对比同个请求服务器是否发生数据变化
            * 缓存响应的标签。 一般和 if-non-match 配合
            * 当首次发送请求成功服务器会返回一个etag
            * 当再次发送该请求 户端会同时发送一个If-None-Match,而它的值就是Etag的值
            * 服务器接受到后对值进行比较
            * 如果相同则 response 的code 就为HTTP_NOT_MODIFIED(304)数据没发生变化直接从缓存里取
            */
           private String etag;
          
           /** Age表示中间环节(CDN或缓存服务器)从服务器拿到数据之后的时间。
            * 如果是客户端看到是0秒,代表是从服务器拿到的最新的数据。。 */
           private int ageSeconds = -1;
           /**工厂类来获取策略 网络 缓存 或者二者都有*
            * @param nowMillis 当前时间
            * @param request 当前请求
            * @param cacheResponse 缓存响应
            */
           public Factory(long nowMillis, Request request, Response cacheResponse) {
             this.nowMillis = nowMillis;
             this.request = request;
             this.cacheResponse = cacheResponse;
             // 如果缓存响应不为空 则更新发送时间戳和响应时间戳
             if (cacheResponse != null) {
               this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
               this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
               Headers headers = cacheResponse.headers();
               // 遍历header 更新 服务响应时间, 缓存响应过期时间,最后响应时间,Etag标签,从服务器拿到数据之后的时间
               for (int i = 0, size = headers.size(); i < size; i++) {
                 String fieldName = headers.name(i);
                 String value = headers.value(i);
                 if ("Date".equalsIgnoreCase(fieldName)) {
                   servedDate = HttpDate.parse(value);
                   servedDateString = value;
                 } else if ("Expires".equalsIgnoreCase(fieldName)) {
                   expires = HttpDate.parse(value);
                 } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
                   lastModified = HttpDate.parse(value);
                   lastModifiedString = value;
                 } else if ("ETag".equalsIgnoreCase(fieldName)) {
                   etag = value;
                 } else if ("Age".equalsIgnoreCase(fieldName)) {
                   ageSeconds = HttpHeaders.parseSeconds(value, -1);
                 }
               }
             }
           }
          
           /**
            * 返回所满足的策略 网络 缓存 或者二者都有
            */
           public CacheStrategy get() {
             CacheStrategy candidate = getCandidate();
          	 // networkRequest对象不为空且禁止使用网络
            if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
              // 返回禁止使用网络且不能使用缓存的缓存策略对象
              return new CacheStrategy(null, null);
            }
          
            return candidate;
          }
          /**
           * 如果请求包含避免服务器发送客户端本地响应的条件,则返回true。
           * 当一个请求与它自己的条件一起排队时,内置的响应缓存将不会被使用。
           * If-Modified-Since 最后响应时间 值为Modified
           * If-None-Match  值为 Etag 标签的值
           *    + 这两个值指示源自请求头If-Modified-Since或If-None-Match指定的版本后未被修改。
           *    + 这意味着不需要重新传输数据,客户端仍有以前缓存的数据
           *
           */
          private static boolean hasConditions(Request request) {
            return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
          }
        }
        
      • 可以看到具体生成规则就在getCandidate()方法中

        •     /** 返回要使用的策略,假设请求可以使用网络。 */
              private CacheStrategy getCandidate() {
                // 没有缓存的响应。则直接返回仅包含网络请求的策略
                if (cacheResponse == null) {
                  return new CacheStrategy(request, null);
                }
          
                // 如果是https 则判断响应是否握手
                // 如果缓存请求缺少握手信息,则直接返回仅包含网络请求的策略
                if (request.isHttps() && cacheResponse.handshake() == null) {
                  return new CacheStrategy(request, null);
                }
          
                //  判断缓存response是否可用于另一个请求(如状态码是否正确等)如果不能用直接返回仅包含网络请求的策略
                if (!isCacheable(cacheResponse, request)) {
                  return new CacheStrategy(request, null);
                }
          
                CacheControl requestCaching = request.cacheControl();
                // 判断缓存控制器是否nocache
                // 或者请求头包含If-Modified-Since( 最后响应时间),If-None-Match(Etag的值)
                // 这两值存在则需要服务器验证本地缓存是不是还能继续使用,所以返回仅包含网络请求的策略;
                if (requestCaching.noCache() || hasConditions(request)) {
                  return new CacheStrategy(request, null);
                }
          
                CacheControl responseCaching = cacheResponse.cacheControl();
                //省略部分代码
                /**
                 * 如果缓存控制器不是nocache,且ageMillis + minFreshMillis < freshMillis + maxStaleMillis
                 *    缓存未过期可用,只是会在响应头添加110或者113的warning)
                 *    则返回一个仅包含缓存的策略
                 */
                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());
                }
          
                // 缓存过期,不可用
                // 如果不存在条件If-None-Match或者If-Modified-Since则 返回一个包仅含网络请求的策略
                // 如果存在条件If-None-Match或者If-Modified-Since将其添加到请求头
                // 返回一个包含网络请求,和使用缓存的策略
                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.
                }
          	 // 构建一个网络请求并设置header 
                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);
              }
          
生成规则总结
  • 注 这里的
    • 返回仅包含网络请求的策略表示 仅使用网络请求
    • 返回仅包含缓存的策略 表示 仅使用缓存数据
    • 返回既包含网络请求又包含缓存的策略 表示 二者都使用
      • 由服务器判断缓存是否过期
      • 没过期则使用缓存并更新缓存
      • 过期了则使用网络并写入缓存 这部分在网络拦截器里
  • 规则总结
  • 使用网络请求策略
    • 1、若当前没有缓存的响应。则直接返回仅包含网络请求的策略
    • 2、 如果是https请求且缺少握手信息则 直接返回仅包含网络请求的策略
    • 3、判断缓存response是否可用于另一个请求(如状态码是否正确等)如果不能用直接返回仅包含网络请求的策略
    • 4 、判断缓存控制器是nocache,或者请求头包含If-Modified-Since,If-None-Match 返回仅包含网络请求的策略
    • 5、缓存过期,不可用,不存在条件If-None-Match或者If-Modified-Since则 返回一个包仅含网络请求的策略
  • 使用缓存策略
    • 1、判断缓存控制器不是nocache,缓存未过期可用,则返回只包含缓存的策略
  • 二者都使用
    • 1、 缓存过期 ,且存在If-None-Match或者If-Modified-Since 则构建一个request并将其添加到reques的header ,此时返回既包含网络请求又包含缓存的策略
      • 此时在缓存拦截器里就要进行304判断 ,来判断数据是否进行过更新了

作用

  • 确定当前请求是使用网络、缓存还是两者都使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值