现在应用市场上的 App 无一不需要网络操作,这些应用的开发者大多数都选择结合使用 OkHttp 和 Retrofit 来完成网络操作。okHttp 最为人称道的一个特性就是它的缓存机制,而我将在本篇博文对其进行剖析。

每次我用 OkHttp 时我都需要一些时间想想我将怎么使用它,我该用哪一个 HTTP 报头,作为一个客户端 App 我有哪些职责,我期望从服务器上获得什么,等等……所以对我来说,这篇博文将是 OkHttp 缓存特性的引用文档,我写这篇文档不是因为我在之后的开发中可能会接触 OkHttp 的缓存机制,而是因为我很有可能在下次需要使用它的时候却把这些知识忘了。

这篇博文用于帮助熟悉 OkHttp 的高级工程师,我希望这篇博文的读者最起码应该知道怎么使用 OkHttp 以及怎么开启缓存。如果你不知道的话,就去 OkHttp 的维基页面学习吧。当然了,这里面的解释可能有一部分是错的,我只是尝试去解释一些我所理解的内容,所以如果你发现我某些部分的解释是有问题的,请通过邮件或其他方式告诉我。

分析的起点

我将会从以下类开始我的分析工作:CacheStrategy 和 HttpEngine,其中 CacheStrategy 包含缓存机制的所有逻辑,HttpEngine 是 CacheStrategy 被调用的地方。

  • CacheStrategy.Factory 理解缓存候选响应的报头并将其转换为类成员的构造器
  • CacheStrategy.getCandidate() - 检查缓存候选响应,如果需要的话讲修改原始请求的报头

这两部分内容是 OkHttp 整个缓存机制最关键的部分,而且几乎包含了所有我们想要了解的信息。虽说其他方法也很重要,但如果你去分析的话会发现最终还是会回到这里。

什么是缓存候选响应?

第一次进行 HTTP 请求时不会存在任何已缓存内容,相关 API 只在有缓存内容的时候才会被调用,这点相信不需要我再多解释了。

一旦响应被存储我们就能尝试将它用到后续的调用中,当然了,我们不可能将所有响应码对应的响应都存储下来。我们只存储以下响应码对应的响应:200, 203, 204, 300, 301, 404, 405, 410, 414, 501, 308。除此以外还有 302, 307,但存储它们对应的响应时必须满足以下条件之一:

  • contains Expires header OR
  • CacheControl contains max-age OR
  • CacheControl contains public OR
  • CacheControl contains private

  • 包含 Expires 报头

  • CacheControl 包含 max-age
  • CacheControl 包含 public
  • CacheControl 包含 private

需要注意:OkHttp 的缓存机制不支持缓存部分报文内容。

当我们重复某个请求,OkHttp 会判断是否已经有已缓存的响应,后文中我将称其为缓存候选响应。

CacheStrategy 是什么?

CacheStrategy 需要一个新的报文和一个缓存候选响应,评估这两个 HTTP 报头是否有效并比较它们。

首先,存放在缓存候选响应报头中的部分成员是:

  • Date
  • Expires
  • Last-Modified
  • ETag
  • Age

下面是需要检查的条件的汇总列表:

  1. 判断缓存候选响应是否存在。
  2. 如果接收的是 HTTPS 请求,如果需要的话,判断缓存候选响应是否已进行握手。
  3. 判断缓存候选响应是否已缓存;这和 OkHttp 存储响应时完成的工作是相同的。
  4. 如果没有缓存,在请求报头的 Cache-Control 中检查对应内容,如果该标记为 true,缓存候选响应将不会被使用,后面的检查也会跳过。
  5. 在请求报头中查找 If-Modified-Since 或 If-None-Match,如果找到其中之一,缓存候选响应将不会被使用,后面的检查也会跳过。
  6. 进行一些计算以得到诸如缓存响应缓存响应存活时间,缓存存活时间,缓存最大失活时间。我不希望在此解释所有对应实现的细节,因为这会让博文变得冗长,你只需要知道以上提到的报文内容(例如:Date, Expires 等等…)还有请求 Cache-Control 中的 max-age, min-fresh, max-stale 这部分相关的计算耗时是毫秒级的。完成检查最简单的办法是写这样的伪代码:if ("cache candidate's no-cache" && "cache candidate's age" + "request's min-fresh" < "cache candidate's fresh lifetime" + "request's max-stale")

如果上述条件被满足,那么已缓存的响应报文将会被使用,后面的检查将跳过。 7. 在需要进行网络操作时,下一次检查会判断它是否为“条件请求”。条件请求指的是:发送报文请求服务器响应,而服务器可能会也可能不会返回新的内容,或让我们使用已缓存的报文。

WE LOOK FOR ANDROID DEVELOPERS!

条件请求

下一节看起来是新的内容,但在代码结构中和上一节所解释内容是在相同的方法里的。

  1. 如果缓存候选响应包含 ETag 报头,那么新的 If-None-Match 请求报文会使用相同的 ETag 值。
  2. 如果上一点没有被满足,且缓存候选响应包含 Last-Modified,那么新的 If-Modified-Since 请求报文会使用该值。
  3. 如果以上两点都没有被满足,且缓存候选响应包含 Date,那么新的 If-Modified-Since 请求报文会使用该值。

还有更多!

上述描述过程的最后,也就是 CacheStrategy.getCandidate() 方法返回后,还会有一个检查。OkHttp 会在此时判断报头的Cache-Strategy 是否包含 “only-if-cached“ 参数。如果有的话,不会请求新的数据。OkHttp 会强制使用缓存候选响应(如果有且可用),不然的话会抛出 HTTP 504 Unsatisfiable Request 错误。

怎么运作?

你可能自己已经知道,进行 HTTP 操作时就是设置正确的报头。但不添加额外的报头内容,使用默认的 HTTP 调用也能完成网络操作,我会在后面提及这部分内容。

无网络环境

刚开始我遇到的问题就包含这个,那时我天真地以为只要允许进行缓存就会解决这个问题,然而并不行,我知道的两种解决方法是:

  • 使用 Cache-Control : only-if-cached header,报文将永远也不会到达服务器。只有存在可用缓存响应时才会检查并返回它。不然的话,会抛出 504 错误,所以开发的时候别忘了处理这个异常。
  • 使用 Cache-Control : max-stale=[seconds] 报头,这种办法更灵活一些,它向客户端表明可以接收缓存响应,只要该缓存响应没有超出其生命周期。而该缓存响应是否可用由 OkHttp 检查,所以不需要与服务器建立连接来判断。但即使如此,当缓存响应超出其生命周期,网络操作还是会进行,然后得到服务器返回的新内容。

关心用户的开销和运行效率

上面提到的解决方法依赖 OkHttp 的验证机制能在离线时完成一些操作。通常 App 都是在线状态,而我们更希望得到新的数据,这样会一次又一次地从服务器上获取相同的内容,给用户产生冗余的开销并降低 App 的运行效率。

理想状态下,我们发送一个请求,得到真正的新数据。当服务器的数据没有发生改变,将得到 304 响应码,而 OkHttp 返回缓存响应。如果服务器支持缓存的话,这种情况是可以实现的。所以你需要做的大体就是和你的同事研究 ETag 或 Last-Modified-Since 是否被支持,如果支持的话,OkHttp 会帮你解决剩下的麻烦!

NOTE: 请不要自己设置 If-Modified-Since 或 If-None-Match。前文已经提到,设置这两个报头会跳过后续的许多检查,进行网络操作。

强制网络操作

你也可以强制进行网络操作,但就我个人而言,我还没有这样用过 OkHttp,不过我发现了一些可能的应用场景。例如你在报头中使用了 max-stale,过了很久服务器接收到了该报文。当你想允许用户在某些特殊情况下强行进行刷新时,很简单,在请求中使用 Cache-Control : no-cache,你就能从服务器得到新的数据了。

结论

在我看来,OkHttp 非常易用,极大简化了网络操作。而且有助于理解数据流,哪些报头能被设置以及它们的用处。我希望这篇博文能给你提供一些帮助。

祝你好运!

剖析OkHttp缓存机制的更多相关文章

  1. 全面剖析Smarty缓存机制一[三种缓存方式]

    今天主要全面总结下Smarty模板引擎中强大的缓存机制,缓存机制有效减少了系统对服务器的压力,而这也是很多开发者喜欢Smarty的原因之一,由于篇幅较大,便于博友阅读,这篇文章将剖析Smarty缓存的 ...

  2. 全面剖析Smarty缓存机制二[清除缓存方法]

    前段时间,写了一篇 Smaryt缓存机制的几种缓存方式 ,详细介绍了三种缓存方式:全局缓存.部分缓存.局部缓存,以及通过is_cache()判断是否存在缓存来进行缓存生成.本来这篇早该完成,由于时间关 ...

  3. WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制

    原文:WCF技术剖析之八:ClientBase<T>中对ChannelFactory<T>的缓存机制 和传统的分布式远程调用一样,WCF的服务调用借助于服务代理(Service ...

  4. 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

  5. [转]Android ListView 与 RecyclerView 对比浅析—缓存机制

    从源码角度剖析ListView 与 RecyclerView 缓存机制的不同 https://zhuanlan.zhihu.com/p/23339185 原文地址:http://dev.qq.com/ ...

  6. Http协议:彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    转载:http://mp.weixin.qq.com/s/uWPls0qrqJKHkHfNLmaenQ 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必 ...

  7. 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存机制

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d3e3ab10c62013697408 作者:黄宁源 一,背景 Recy ...

  8. Java缓存学习之三:CDN缓存机制

    CDN是什么? 关于CDN是什么,此前网友详细介绍过. CDN是Content Delivery Network的简称,即"内容分发网络"的意思.一般我们所说的CDN加速,一般是指 ...

  9. 深入剖析Tomcat会话机制

    1缓存机制 Tomcat默认将Session保存到内存中.但同时,Tomcat也提供了PersistentManager配合不同的Store实现的方式,使Session可以被保存到不同地方(Datab ...

随机推荐

  1. vim中如何引用自定义模板文件

    我们在使用vim新建文件时可以引用自定义模板,来避免重复的数据格式处理花费太多时间. 实现方法很简单,只需要2步即可:1. 在.vim/template目录放入自己的模板文件(如shellconfig ...

  2. CoreData的简单使用(一)数据库的创建

    iOS有多种数据持久化得方式 plist文件(属性列表) preference(偏好设置,NSUserDefaults) NSKeyedArchiver(归档,用的不多) SQLite 3 (需要导入 ...

  3. a cold welcome

    What does 'a cold welcome' refer to? On wednesday evening,we went to the town hall. It was the last ...

  4. iOS开发推送--客户端 服务端

    1.推送过程简介 (1)App启动过程中,使用UIApplication::registerForRemoteNotificationTypes函数与苹果的APNS服务器通信,发出注册远程推送的申请. ...

  5. 机器人学 —— 机器人视觉(Bundle Adjustment)

    今天完成了机器人视觉的所有课程以及作业,确实是受益匪浅啊! 最后一个话题是Bundle Adjustment. 机器人视觉学中,最顶尖的方法. 1.基于非线性优化的相机位姿估计 之前已经在拟合一篇中, ...

  6. Android应用开发学习笔记之菜单

    作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz Android中的菜单分为选项菜单(OptionMenu)和上下文菜单(Context Menu).通常使用菜单资源 ...

  7. 可辨别iPhone真假的网址

    在如下的网址中输入iPhone的序列号,可知道该iPhone的型号,生产日期,激活状态等. 1.http://www.app111.org/ 2.http://act.weiphone.com/wet ...

  8. POJ 1322 Chocolate(母函数)

    题目链接:http://poj.org/problem?id=1322 题意: 思路: double C[N][N]; void init() { C[0][0]=1; int i,j; for(i= ...

  9. Introduction

    http://www.entityframeworktutorial.net/EntityFramework5/entity-framework5-introduction.aspx Basics o ...

  10. 第九篇 ERP实施项目中需求分析及方案设计的通用思路

    顾问实施ERP就好想医生给患者看病抓药,不但具有类似的过程,而且具有其通用的思路. --详见http://bbs.erp100.com/thread-272856-1-1.html 顾问实施ERP就好 ...