OkHttp之BridgeInterceptor简单分析 》简单分析了BridgeInterceptor的工作原理,在Okhttp的拦截器链上BridgeInterceptor的下一个拦截器就是CacheInterceptor,所以本文就对此拦截器的功能做一个简单的梳理。顾名思义,该拦截器的工作跟缓存有关。

正如浏览器在访问网络请求的时候提供缓存功能一样,Okhttp也提供了缓存功能,使用Okhttp的缓存其实跟浏览器的缓存有相似之处;不像使用图片缓存框架那样,如果有缓存图片框架会直接使用缓存中的图片(比如ImageLoader),而再继续网络请求;而浏览器不一样,即使有缓存也会再次发起网络请求,下面就简单说下网络请求缓存的一些基本知识,算是为本文对CacheInterceptor的分析做铺垫。

在服务器响应的时候,在”响应头信息”里面包含了资源的最后修改时间,我们可以通过Last-Modified这个header来获取该时间:



既然我们能拿到资源文件的最后修改时间,那么我们就可以根据这个时间来判断服务器上面的资源是否有修改了!怎么做呢?当我们再次请求服务器的时候,在”请求头信息”会带上次请求获取的Last-Modified时间,该时间在请求头信息”用If-Modified-Since 这个header里面,见下图:



当服务器收到请求后检测到If-Modified-Since这个Header,则与被请求资源的最后修改时间进行比对:

if(服务器资源最后修改时间<=If-Modified-Since) {
  说明对应的资源没有修改过,此时服务器返回304状态码,不再需要将报文主体部分返回给客户端;
  客户端此时直接用缓存的资源即可
}else {
   说明资源又被改动过,服务器返回状态码200,此时就需要将报文的主体部分返给客户端,;
}

其实,通过上面的图我们发现在请求/响应的头信息里面还有两个Header:If-None-Match/Etag。当第一次请求的时候,”响应头信息”里面有一个Etag的Header可以看做是资源的标识符。再次请求的时候,”请求头信息”会包含一个If-None-Match的头信息。此时服务器取得If-None-Match后会和资源的Etag进行比如,如果相同则说明资源没改动过,那么响应304,客户端可以使用缓存;否则返回200,并且将报文的主题返回给客户端(Etag的说明可以参考百度百科)

可以简单的举个例子:



清空浏览器缓存,随便用浏览器发出一个http请求,得到如下图所示的结果:

刷新浏览器再次发起请求的时候,则因为缓存的作用会有如下结果:

到此为止,简单的分析完了浏览器缓存的那些事儿,下面带着上面的一些基本理论来正式分析一下CacheInterceptor的源码,当然仍然是分析其intercept方法:

Response intercept(Chain chain) throws IOException {
    //如果配置了缓存:优先从缓存中读取Response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    long now = System.currentTimeMillis();
    //缓存策略,该策略通过某种规则来判断缓存是否有效
   CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    。。。。
    //如果根据缓存策略strategy禁止使用网络,并且缓存无效,直接返回空的Response
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          。。。
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)//空的body
          。。。
          .build();
    }

    //如果根据缓存策略strategy禁止使用网络,且有缓存则直接使用缓存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    //需要网络
    Response networkResponse = null;
    try {//执行下一个拦截器,发起网路请求
      networkResponse = chain.proceed(networkRequest);
    } finally {
      。。。
    }

    //本地有缓存,
    if (cacheResponse != null) {
      //并且服务器返回304状态码(说明缓存还没过期或服务器资源没修改)
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //使用缓存数据
        Response response = cacheResponse.newBuilder()
            。。。
            .build();
          。。。。
         //返回缓存
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    //如果网络资源已经修改:使用网络响应返回的最新数据
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //将最新的数据缓存起来
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {

        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      。。。。
   //返回最新的数据
    return response;
  }

简单的总结下上面的代码都做了些神马:

1、如果在初始化OkhttpClient的时候配置缓存,则从缓存中取caceResponse

2、将当前请求request和caceResponse 构建一个CacheStrategy对象

3、CacheStrategy这个策略对象将根据相关规则来决定caceResponse和Request是否有效,如果无效则分别将caceResponse和request设置为null

4、经过CacheStrategy的处理(步骤3),如果request和caceResponse都置空,直接返回一个状态码为504,且body为Util.EMPTY_RESPONSE的空Respone对象

5、经过CacheStrategy的处理(步骤3),resquest 为null而cacheResponse不为null,则直接返回cacheResponse对象

6、执行下一个拦截器发起网路请求,

7、如果服务器资源没有过期(状态码304)且存在缓存,则返回缓存

8、将网络返回的最新的资源(networkResponse)缓存到本地,然后返回networkResponse.

根据博客《Okhttp源码解析》得出的拦截器执行图(下图):



CacheInterceptor返回的Response交给拦截器链前面的一个拦截器,即BridgeInterceptor来处理,那么这个拦截器是怎么处理的呢,在《OkHttp之BridgeInterceptor简单分析 》这篇博客的最后有了简单的说明:

 Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //判断服务器是否支持gzip压缩格式,如果支持则交给kio压缩
    if (transparentGzip&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();

BridgeInterceptor拿到CacheInterceoptor返回的response之后根据请求头的Content-Encoding类型来决定返回的类型,如果是gzip,则将response经过GzipSource 处理后将其Response的body设置为RealResponseBody对象,否则就简单的返回response。

简单流程如下:

到此本篇博客结束,如有不当之处,欢迎批评指正,共同学习

Okhttp之CacheInterceptor简单分析的更多相关文章

  1. OkHttp之ConnectInterceptor简单分析

    在< Okhttp之CacheInterceptor简单分析 >这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览 ...

  2. Okhttp之CallServerInterceptor简单分析

    在Okhttp源码分析专栏的几篇博客分析了Okhttp几个拦截器的主要功能,还剩下最后一个拦截器CallServerInterceptor没有分析,本篇博客就简单分析下该拦截器的功能. 在Okhttp ...

  3. OkHttp之BridgeInterceptor简单分析

    在< Okhttp源码简单解析(一) >这篇博客简单分析了Okhttp请求的执行流程,通过该篇博客我们知道OkHttp的核心网络请求中内置"拦截器"发挥了重大作用:本篇 ...

  4. Okhttp之连接池ConnectionPool简单分析(一)

    开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解: <Okht ...

  5. Okhttp对http2的支持简单分析

    在< Okhttp之RealConnection建立链接简单分析>一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开t ...

  6. Okhttp之RealConnection建立链接简单分析

    在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...

  7. Okhttp之RouteSelector简单解析

    继前面的几篇OKhttp的拦截器简单分析之后,对于后续Okhttp之间的分析自己也着实琢磨了一段时间,是分析RealConnection?还是ConnectionPool,随着对Okhttp源码的深入 ...

  8. 简单分析JavaScript中的面向对象

    初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...

  9. CSipSimple 简单分析

    简介 CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话.连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果 ...

随机推荐

  1. Response attachment filename 中文乱码

    Response.setHeader("Content-Disposition", "attachment; filename=" + fileName+&qu ...

  2. [BZOJ2599]Race

    Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k 第二 ...

  3. SaltStack配置salt-api第十二篇

    介绍 SaltStack官方提供有REST API格式的 salt-api项目,将使Salt与第三方系统集成变得尤为简单.本文讲带你了解如何安装配置Salt-API, 如何利用Salt-API获取想要 ...

  4. spring 概念理解

    一.Spring的IoC(Inversion of Control).这是Spring中得有特点的一部份.IoC又被翻译成“控制反转”,也不知道是谁翻译得这么别扭,感觉很深奥的词.其实,原理很简单,用 ...

  5. LeetCode——Coin Change

    Question You are given coins of different denominations and a total amount of money amount. Write a ...

  6. 初入spring boot(七 )Spring Data JPA

    Spring Data JPA通过提供基于JPA的Repository极大地减少JPA作为数据访问方案的代码量. 1.定义数据访问层 使用Spring Data JPA建立数据访问层十分简单,只需定义 ...

  7. sql server 数据字典的妙用

    以下提到的一些需求,你可能会说用SQL server management studio 客户端 看一下不就可以了,何必这么麻烦 ! 是的,当1 个表的时候我们可以人工检查,但当有10 个表,100个 ...

  8. ZeroMq实现跨线程通信

    ZeroMq实现跨线程通信 之前在技术崇拜的技术经理指导下阅读了ZeroMq的基础代码,现在就将阅读的心得与成果记录一下,并重新模仿实现了一下经理的异步队列. 1.对外接口 //主要接口(1)void ...

  9. Floyd判圈算法 Floyd Cycle Detection Algorithm

    2018-01-13 20:55:56 Floyd判圈算法(Floyd Cycle Detection Algorithm),又称龟兔赛跑算法(Tortoise and Hare Algorithm) ...

  10. mysql 命令行参数说明

    mysql --help -h : 主机名 -u : 用户名 -p : 密码 -P : 端口,不写默认(3306) -D :指定数据库(一般不指定) -e :查询语句 --column-names : ...