美文网首页
OkHttp3.0缓存机制及应用1

OkHttp3.0缓存机制及应用1

作者: 走在冷风中吧 | 来源:发表于2019-08-23 18:21 被阅读0次

前言: 最近再学习http的缓存策略, 深入到android网络框架okhttp3中进行了解. 未进行学习的可先移步至okhttp缓存实现过程图

关键类是: CacheInterceptor

1. 这个类从哪儿来? 什么时候用到的?

这个类的使用是在Okhttp3的RealCall类中. 首先了解下RealCall类, 这个类是Call的子类. 一个Call就是一个待执行的request, Call接口中有个execute方法, 用来执行request请求.
下面是RealCall中的excute方法:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
//注意这里有个连接器链的方法
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

每次执行request请求的时候, 都会走getResponseWithInterceptorChain();方法, 我们去看看里面写了什么

  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()));
//这里添加了若干个拦截器进来,我们只对cacheIntercept进行了解
    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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

以上我们了解到在执行request请求的时候, okhttp3为我们添加了缓存拦截器, 我们来分析下拦截器中都做了些什么

2. CacheInterceptor
  @Override public Response intercept(Chain chain) throws IOException {
//这里获取到内部缓存候选对象, 没有为null, 有则取出response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
//这一行很重要, 根据当前chain中的response和缓存中的response指定当前的缓存策略--request和response, request为null代表不使用网络, response代表没有本地缓存
// 会在后文中介绍缓存策略的具体内容
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

//缓存的计数器+1
    if (cache != null) {
      cache.trackResponse(strategy);
    }

//候选缓存存在, 且本地cache不存在, 关闭候选缓存的输入输出流
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //如果禁用了网络且本地的cache也为空 请求失败, 返回504(only-if-cached代表不请求网络, 强制使用缓存)
    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();
    }

    // 不使用网络 直接返回本地的cache
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
         .cacheResponse(stripBody(cacheResponse))
          .build();
    }

//如果走的这一步 , 说明需要请求网络, 接下来会进行网络reponse的处理

    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());
      }
    }
    //读完最后的缓存策略 可以知道会在请求头中根据本地缓存数据的header对request的请求header进行处理, 符合条件是会进行对比缓存
    // 这里用来处理网络请求返回304状态, 304代表缓存命中
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果有本地缓存, 且网络请求命中缓存, 则将本地缓存和网络缓存的的头部进行合并后返回response
//重新设置response的请求响应时间等一系列参数
        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()).
//缓存命中次数+1
        cache.trackConditionalCacheHit();
//更新本地缓存信息
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

//若没有命中缓存, 则使用网络请求返回的response做响应结果
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
//如果开启了缓存, 并且body不为空, 这里只有get请求会被缓存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response); 17744567781
        return cacheWritingResponse(cacheRequest, response);
      }
//缓存方法无效时 会把缓存移除
//无效方法有:  return method.equals("POST")
  //      || method.equals("PATCH")
    //    || method.equals("PUT")
      //  || method.equals("DELETE")
        //|| method.equals("MOVE"); 
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

总上可以看出来, CacheIntercepter根据CacheStrategy中的internetRequest和cacheResponse来对使用本地缓存或者网络数据来进行判断并返回合适的response, 同时更新本地的缓存, 接下来我们就需要知道缓存策略是如何生成的

3. CacheStrategy

Factory方法:

    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
//这里的cacheResponse就是指本地的缓存
      this.cacheResponse = cacheResponse;
 //如果本地缓存不为空, 读取本地缓存的头部信息, 用作以后时效性等对比
      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        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);
          }
        }
      }
    }

在CacheIntercepter调用了一个获取缓存策略的方法get(), 看到get方法中有个getCandidate()方法

    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;
    }
private CacheStrategy getCandidate() {
//在这里说明下, CacheStrategy(request, response)中的两个参数
//下面是源码中的注释:
/**The request to send on the network, or null if this call doesn't use the network. */
 /** The cached response to return or validate; or null if this call doesn't use a cache. */
//request: 当request为null时则不使用网络,反之则使用
//response: 当response为null时不使用缓存,反之则使用

      // 本地缓存为空, 直接采用网络请求, 使用网络数据
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

//请求是https协议, 但是缓存未使用三次握手机制, 则使用网络
//注意: 这里就涉及到了https安全机制的一些问题, 在我的别的帖子中会有说明
      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.
//根据response和request中的header缓存参数以及响应码判断是否可以使用缓存, 不能使用缓存则请求网络
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

//获取请求中的缓存策略, 如果请求是noCache或者有使用对比缓存字段 If-None-Match或者If-Modified-Since, 则使用去请求网络
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

//responseCaching的header中如果有immutable字段, 代表这个缓存永远不会过期, 则直接使用缓存数据 
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

//以下是一系列从cacheResponse中获取出来的时效性的相关的一些参数
      long ageMillis = cacheResponseAge();
      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());
      }
//对于时效性进行判断, 有缓存但是缓存有过期时, header中添加警告信息后返回数据
      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());
      }

      //如果本地缓存中的header头中有相关缓存对比的字段, 则取出来放置request的头中, 发起下次请求
//这里可以看出来 Etag的优先级要比Last-Modified高
      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();
//返回经过处理的request和本地缓存
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

这里我们学习了okhttp3为我们实现的缓存策略, 究竟我们如何将这些缓存策略应用到项目中呢?
下篇简书中会对缓存的应用进行介绍, 敬请期待~

相关文章

  • OkHttp3.0缓存机制及应用1

    前言: 最近再学习http的缓存策略, 深入到android网络框架okhttp3中进行了解. 未进行学习的可先移...

  • http缓存相关

    重点推荐 浏览器缓存知识小结及应用 第一步:HTTP缓存实现的原理] 第二步:浏览器 HTTP 协议缓存机制详解...

  • Redis应用场景及缓存问题

    1.应用场景 (1) 缓存 缓存机制几乎在所有的大型网站都有使用,合理地使用缓存不仅可以加快数据的访问速度,而...

  • Glide源码分析

    一、源码分析:1、with()2、load()3、into()二、缓存机制1、Glide缓存机制简介1.1缓存的图...

  • 全站加速知识

    1.HTTP缓存机制及原理[https://www.cnblogs.com/chenqf/p/6386163.ht...

  • 数据持久化

    数据持久化及数据更新缓存 常用的8种缓存机制:HTTP缓存, locationStorage, Session S...

  • 浏览器缓存机制2-应用缓存

    浏览器缓存机制2-应用缓存 在公司项目中,一些移动版的WEB页面恰好用到了应用缓存,故顺便写篇文章来总结下应用缓存...

  • JS 浏览器缓存

    缓存优点 根据缓存位置区分的四种缓存 缓存过程分析 缓存策略 缓存机制 实际应用场景 用户行为对浏览器缓存的影响 ...

  • MySQL查询缓存

    1.概述: MySQL缓存机制简单的说就是缓存sql文本及查询结果,如果运行相同的SQL,服务器直接从缓存中取到结...

  • 浏览器的缓存机制

    一、缓存机制概述 1. 基本概念介绍 浏览器缓存机制(即HTTP缓存机制),根据HTTP报文的缓存标识来决定使用缓...

网友评论

      本文标题:OkHttp3.0缓存机制及应用1

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