okhttp缓存分析

2018-07-13  本文已影响103人  鹈鹕醍醐

在实际项目开发中,数据的缓存是十分有必要的,一来可以减少服务器端查询压力,二来也能加速反馈时间,在网络良好时实现秒回。

服务端的缓存处理
服务端无论采用php或者java实现,在业务层都可以使用非常成熟的MemCacheRadis缓存框架,合理配置缓存以减少数据库压力,在发送响应时通过header中的Cache_Control实现更精准的缓存控制策略

1.Header中的"Cache_Control"

fiddler截获的请求与响应中header示例
这个接口在Request和Response中都返回了Cache_Control字段,科普下字段对应取值的含义

使用Builder#cacheControl(CacheControl)构建Request"Cache-Control"
使用CacheControl#parse(Headers)构建Response"Cache-Control"

关于 Pragma:no-cache

Pragma:no-cacheCache-Control: no-cache相同。Pragma: no-cache兼容http 1.0 ,Cache-Control: no-cache是http 1.1提供的。因此,Pragma: no-cache可以应用到http 1.0 和http 1.1,而Cache-Control: no-cache只能应用于http 1.1.


"Cache-Control"其实只是请求的相应标记,告诉服务器/客户端所需的缓存策略。客户端具体的缓存执行对象是Cache

2.OkHttp中的 Cache 解析

看一看RealCallCacheInterceptor的添加顺序:


  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    ······
    interceptors.add(new CacheInterceptor(client.internalCache()));
    ······
    interceptors.addAll(client.networkInterceptors());
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

Interceptor.Chain#(Request)的处理逻辑如下

RealCall中获取网络响应
通常client.interceptors()client.networkInterceptors()只包含客户端手动定义的拦截器,分析CacheInterCeptorintercept如下(代码经过精简,只保留关键模块)
@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request()) : null;
    ······
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    ······
    如下为强制使用缓存 却又没有获取到:返回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();
    }

   如下 强制使用缓存并命中,链式调用结束,直接返回缓存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
     ······如下 通过Chain调用CallServerInterceptor获取网络响应
     Response  networkResponse = chain.proceed(networkRequest);
     ······
    如下 有缓存且网络返回code=304(not modified),则组合并刷新cache后返回组合响应
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        ······
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    写入新缓存,对于POST/PATCH/PUT/DELETE/MOVE不会缓存
    满足指定的条件则执行缓存cache
   if (HttpHeaders.hasBody(response)) {  //有body
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }
    return response;
  }

在代码的最后找到了缓存指令,贴出需要的条件maybeCache()

  private CacheRequest maybeCache(Response userResponse, Request networkRequest,
      InternalCache responseCache) throws IOException {
    if (responseCache == null) return null;

    如果request与response的head中都没有设置"no-cache",则可以缓存,否则从缓存中移除
    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
      对POST/PATCH/PUT/DELETE/MOVE方法 移除缓存,get未做处理
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
          responseCache.remove(networkRequest);
      }   ······
      return null;
    }添加进缓存中
    return responseCache.put(userResponse);
  }

分析到这里OkHttp的缓存模型已经基本结束了,结合源码, 我们可以:

3. 通过Interceptor定制客户端自己的缓存策略

OkHttpClient.Builder builder = new OkHttpClient.Builder();
Interceptor requestInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        if (isNetWorkDisConnected) {
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }
        return chain.proceed(request);
    }
};
Interceptor responseInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        if (chain.request().cacheControl().noCache()
                || !response.cacheControl().noCache()) {
            return response;
        }//对于不需要缓存的request和服务端配置了缓存的response,直接放行
        if (isNetWorkDisConnected) {
            CacheControl control = new CacheControl.Builder().build();
            return response.newBuilder()
                    .header("Cache-Control", "public, max-age=60")
                    .removeHeader("Pragma") 
                  //CacheControl.parse(response.Header)时,Pragma字段会造成干扰
                    .build();
        } else {
            return response.newBuilder()
                    .header("Cache-Control", "public, only-if-cached, max-stale=0")
                    .removeHeader("Pragma")
                    .build();
        }
    }
};
OkHttpClient client = builder.addInterceptor(requestInterceptor)
        .addNetworkInterceptor(responseInterceptor)
        .build();

写到这里告一段落吧,leader催着发周报,cookieJar这块就不贴了,以后放另一个模块写。部分逻辑借鉴自
https://www.jianshu.com/p/2821000526df,前人栽树后人乘凉,在此表示感谢

上一篇下一篇

猜你喜欢

热点阅读