美文网首页
okhttp缓存分析

okhttp缓存分析

作者: 鹈鹕醍醐 | 来源:发表于2018-07-13 18:20 被阅读103次

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

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

    1.Header中的"Cache_Control"

    fiddler截获的请求与响应中header示例
    这个接口在Request和Response中都返回了Cache_Control字段,科普下字段对应取值的含义
    • max-age:告诉浏览器页面将缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新
    • s-max-age:这个参数告诉缓存服务器(proxy,如Squid)的缓存页面的时间。如果不单独指定,缓存服务器将使用max-age。
    • must-revalidate:这告诉浏览器,一旦缓存的内容过期,一定要向服务器询问是否有新版本。
    • proxy-revalidate:proxy上的缓存一旦过期,一定要向服务器询问是否有新版本。
    • no-cache:不做缓存。
    • no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。
    • public:告诉缓存服务器, 即便是对于不该缓存的内容也缓存起来。
    • private:告诉proxy不要缓存,但是浏览器可使用private cache进行缓存。一般登录后的个性化页面是private的。
    • no-transform: 告诉proxy不进行转换,比如告诉手机浏览器不要下载某些图片。
    • max-stale: 指示客户端可以接收超时多久的响应消息。

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

    • 开发中常用的几个属性:"max-age","no-cache","max-stale"

    关于 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,前人栽树后人乘凉,在此表示感谢

    相关文章

      网友评论

          本文标题:okhttp缓存分析

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