okhttp源码解析(三):缓存机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011315960/article/details/81181637

前言

研究完自定义View,我们接着看okhttp的源码,之前已经讨论了okhttp的使用流程,和网络数据的读取,这一次来看一看okhttp的缓存机制。

正文

首先我们回到之前已经阅读过的InterceptorChain,RealCall.java中的getResponseWithInterceptorChain()方法:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    // 重试的Interceptor,在构造方法中创建
    interceptors.add(retryAndFollowUpInterceptor);
    // 桥接Interceptor,主要对request的header进行操作,添加一些header信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 缓存Interceptor,判断是否要使用缓存
    // 如果使用缓存,直接返回缓存的response,后面的Interceptor就不会得到执行
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 连接Interceptor
    interceptors.add(new ConnectInterceptor(client));
    // 我们在okhttp自定义的interceptor,不设置的话,可以忽略
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    // 呼叫服务端的Interceptor,主要是读取从服务端返回的数据
    interceptors.add(new CallServerInterceptor(forWebSocket));
    // Interceptor递归的开始
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

通过注释我我们顺便复习一下责任链的流程:

自定义Interceptor(可选) -> 重试Interceptor -> 桥接(准备)Intercepto -> 缓存Interceptor -> 链接Interceptor -> 自定义网络任务Interceptor(可选) -> 读取Interceptor

今天我们分析的就是缓存的CacheInterceptor,首先我们看到了他的构造方法里传入OkHttpClient.internalCache(),这个Cache需要在构建OkHttpClient的传入,默认是空的,我们可以自己实现InternalCache接口,自定义缓存的机制,也可以使用okhttp自带的Cache类进行缓存:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .cache(new Cache(File, maxSize)).build();

Cache的构造方法有两个:

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);
}

我们看到最终调用的还是创建DiskLruCache,这个类大家一定都不陌生,这是开源的缓存文件的类,他内部有完善的算法,帮助我们删除最不需要的缓存的对象,保持固定的缓存大小,节省用户的硬存空间。

其中FileSystem这个参数用的非常少,他主要负责从DiskLruCache读写缓存数据,你可以自定义实现FileSystem接口,或者直接使用okhttp内部实现的FileSystem.SYSTEM。

一般我们只是用第一个构造方法,既然了解了他的缓存使用的是DiskLruCache,我们就没有看下去的必要了。所以直接回到CacheInterceptor:

// 是否有缓存,request是key
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    // 记录当前时间
    long now = System.currentTimeMillis();
    // 把request和repsonse封装到起来,判断是否符合要求
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    if (cache != null) {
      // 增加计数器
      cache.trackResponse(strategy);
    }

    // 如果从cache中得到了缓存,但是缓存策略中却没通过,说明这个缓存无效
    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.
    // 如果request不使用网络,但是缓存也不充分,直接返回504
    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();
    }

    // 如果仅仅需要缓存,这里就直接返回缓存的response就可以了
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

以上是执行chain.proceed(networkRequest)之前的代码,看看他都做了哪些工作。

首先从缓存中取出之前的缓存,然后把request和取出的response封装到CacheStrategy.Factory中处理,判断我们的request和response是否符合要求,贴出判断的代码:

private CacheStrategy getCandidate() {
      // No cached response.
      // 没有缓存Response
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // Drop the cached response if it's missing a required handshake.
      // 如果是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
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      // 判断是否要使用缓存,不使用,response等于null
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();
      // 是否请求的response是不可改变的,这个值默认都是false
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }
      // 计算缓存的存活时间
      long ageMillis = cacheResponseAge();
      // 计算缓存存活的时间
      long freshMillis = computeFreshnessLifetime();
      // 如果request缓存有设置存活时间,取最小值
      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());
      }

      // 如果使用缓存,且存活时间+最小存活时间 < 存活时间 + 最大腐败时间,使用缓存
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        // 存活时间+最小刷新时间 < 可以刷新的时间
        if (ageMillis + minFreshMillis >= freshMillis) {
          // 添加header信息,缓存已经开始腐败了
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        // 如果存活时间大于一天 且 不需要过期
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          // 添加header信息,希望能过期
          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.
      }
      // 找到对应的缓存情况,把之前的header添加到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);
}

public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();
      // 这里判断网络请求,是否只能使用缓存,这里是不支持的,所以之后会抛出504异常
      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;
}

代码中判断各种情况,信息都是从request的header中和response的header中得到的信息,他们被封装到CacheControl类中。

如果我们的request请求是合法的,之后就会通过责任链得到最终的结果response:

Response networkResponse = null;
        try {
            // 得到最终的response结果
            networkResponse = chain.proceed(networkRequest);
        } finally {
            // If we're crashing on I/O or otherwise, don't leak the cache body.
            // 判断如果得到的response是空的,但是缓存却不为空,扔掉缓存
            if (networkResponse == null && cacheCandidate != null) {
                closeQuietly(cacheCandidate.body());
            }
        }

        // 如果之前有缓存的response
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
            // http code 304 是请求网页的返回值,表示从上次请求后,网页的内容没有发生任何变化
            // 客户端可以忽略此处判断
            if (networkResponse.code() == HTTP_NOT_MODIFIED) {
                Response response = cacheResponse.newBuilder()
                        // 混合了request和response的header信息
                        .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 response = networkResponse.newBuilder()
                // 清空之前的缓存
                .cacheResponse(stripBody(cacheResponse))
                // 清空请求到的内容
                .networkResponse(stripBody(networkResponse))
                .build();
        // 如果缓存池不等于null
        if (cache != null) {
            // 如果这个response是可以被缓存的
            if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
                // Offer this request to the cache.
                // 更新缓存信息
                CacheRequest cacheRequest = cache.put(response);
                return cacheWritingResponse(cacheRequest, response);
            }
            // 如果请求的方法不需要缓存,移除缓存,例如post,put
            if (HttpMethod.invalidatesCache(networkRequest.method())) {
                try {
                    cache.remove(networkRequest);
                } catch (IOException ignored) {
                    // The cache cannot be written.
                }
            }
        }

        return response;

之后的操作比较简单,我们把得到的结果response复制一份,关闭连接的response,然后根据情况更新或清除缓存。

总结

okhttp的缓存机制意料之外的简单,他只有硬存缓存,并没有内存缓存,这是他缓存机制的一大缺陷,当然我们可以通过自定义缓存机制来解决这一问题。

下一篇我们来研究一下okhttp的重试机制。

猜你喜欢

转载自blog.csdn.net/u011315960/article/details/81181637
今日推荐