Android 笔记: OkhttpInterceptor CacheInterceptor

Anroid OKhttp笔记1 流程分析
Android OkhttpInterceptor 笔记:RetryAndFollowUpInterceptor
Android OkhttpInterceptor 笔记:BridgeInterceptor
Android OkhttpInterceptor 笔记:ConnectInterceptor
Android OkhttpInterceptor 笔记:CacheInterceptor
Android OkhttpInterceptor 笔记:CallServerInterceptor
Android Okhttp笔记:ConnectionPool
Android Okhttp3:Dispatcher分析笔记


一、流程代码逻辑分析

CacheInterceptor是okhttp中缓存拦截器,是负责http请求的缓存处理 ,流程:
1.读取缓存
2.创建缓存策略,强制缓存、对比缓存等,
3.根据策略,不使用网络,又没有缓存的直接报错,并返回错误码504。
4.根据策略,不使用网络,有缓存的直接返回。
5.前面两个都没有返回,继续执行下一个Interceptor,即ConnectInterceptor。
6.接收到网络结果,如果响应code式304,则使用缓存,返回缓存结果。
7.读取网络结果。
8.对数据进行缓存。
9.返回网络读取的结果。

其中header 强制缓存使用的的两个标识:
Expires:Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。到期时间是服务端生成的,客户端和服务端的时间可能有误差。
Cache-Control:Expires有个时间校验的问题,所以HTTP1.1采用Cache-Control替代Expires。

Cache-Control的取值:

private::客户端可以缓存。
public::客户端和代理服务器都可缓存。
max-age=xxx: 缓存的内容将在 xxx 秒后失效
no-cache::需要使用对比缓存来验证缓存数据。
no-store:所有内容都不会缓存;强制缓存,对比缓存都不会触发。

1.流程分析

当从上个拦截器中获取到http请求时,会从缓存里面取出对应的响应(之前缓存过的),如果没有,返回null。然后会根据request和获取到的缓存的response生成一个缓存策略CacheStrategy。

//如果配置了缓存:优先从缓存中读取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;

缓存策略器是使用缓存还是请求网络获取新的数据。内部有两个属性:networkRequest和cacheResponse,在 CacheStrategy 内部会对这个两个属性在特定的情况赋值。

networkRequest:若是不为 null ,表示需要进行网络请求
cacheResponse:若是不为 null ,表示可以使用本地缓存

//如果根据缓存策略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;

2.CacheStrategy

策略器,负责判断是使用缓存还是请求网络获取新的数据。

获取方法

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

其中 cacheCandidate它表示的是从缓存中取出的 Response 对象,有可能为null(在缓存为空的时候),在 new CacheStrategy.Factory 内部如果 cacheCandidate 对象不为 null ,那么会取出 cacheCandidate 的头信息,并且将其保存到 CacheStrategy 属性中。

get()

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() {
  // No cached response.
  if (cacheResponse == null) {
    return new CacheStrategy(request, null);
  }
  // Drop the cached response if it's missing a required handshake.
  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.
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }
  CacheControl requestCaching = request.cacheControl();
 //....
}

代码显示的几种情况都是要发起请求的,包括:
没有对应的缓存结果;
https请求却没有握手信息;
不允许缓存的请求(包括一些特殊状态码以及Header中明确禁止缓存)。

3.实现缓存

 // 存入到缓存中去
    CacheRequest cacheRequest = cache.put(response); 


  @Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {
  try {
    remove(response.request());
  } catch (IOException ignored) {
    // The cache cannot be written.
  }
  return null;
}
if (!requestMethod.equals("GET")) {
  // Don't cache non-GET responses. We're technically allowed to cache
  // HEAD requests and some POST requests, but the complexity of doing
  // so is high and the benefit is low.
  return null;
}
if (HttpHeaders.hasVaryAll(response)) {
  return null;
}
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
  editor = cache.edit(key(response.request().url()));
  if (editor == null) {
    return null;
  }
  entry.writeTo(editor);
  return new CacheRequestImpl(editor);
} catch (IOException e) {
  abortQuietly(editor);
  return null;
}
 }

其中 invalidatesCache()

   public static boolean invalidatesCache(String method) {
return method.equals("POST")
    || method.equals("PATCH")
    || method.equals("PUT")
    || method.equals("DELETE")
    || method.equals("MOVE");     // WebDAV

}
对方法是POST,PATCH,PUT,DELETE,MOVE的请求,将缓存清除掉,这些是不应该被缓存的。然后再明确确认GET方法才会被缓存。
然后由 Cache.Entry 构造的entry writeTo把editor 写入,其实就是将请求信息按顺序写入到DiskLruCache中,最终由DiskLruCache写入到磁盘中。


Anroid OKhttp笔记1 流程分析
Android OkhttpInterceptor 笔记:RetryAndFollowUpInterceptor
Android OkhttpInterceptor 笔记:BridgeInterceptor
Android OkhttpInterceptor 笔记:ConnectInterceptor
Android OkhttpInterceptor 笔记:CacheInterceptor
Android OkhttpInterceptor 笔记:CallServerInterceptor
Android Okhttp笔记:ConnectionPool
Android Okhttp3:Dispatcher分析笔记

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容