OkHttp3之缓存应用

1、概论

在上一篇文章里面,我们详细的剖析了HTTP协议的缓存机制。但那主要是从服务器端进行分析的,这有助于我们理解HTTP的缓存机制,并为我们用好OkHttp3这一客户端的封装库提供更为清晰的思路。知其原理,才能事半功倍。如果对于HTTP协议的缓存机制还不是很不清楚,可以去看下上一篇博客HTTP协议进阶之缓存

本篇文章主要从客户端的缓存控制出发,探讨如何利用OkHttp3是如何对缓存进行控制的。

2、请求报文的Cache-Control

在写代码之前,我们先通过下表大概回顾一下HTTP请求报文中与缓存相关的首部字段Cache-Control中的值都有哪些,它们的作用分别都是什么,它们的详细解释在上一篇博客HTTP协议进阶之缓存中:

指令 目的
max-stale=<s> 在<s>时间段内,文档不会过期。该指令放松了缓存的规则
min-fresh=<s> 至少在未来的<s>秒内文档要保持新鲜。这使缓存规则更加严格了
max-age=<s> 缓存无法返回缓存时间长于<s>秒的文档。这条指令会使规则更加严格,除非同时还发送了max-stale指令,在这种情况下,使用期可能会超过其过期时间。
no-cache 除非资源进行了再验证,否则这个客户端不会接受已缓存的资源
no-store 缓存应该尽快从存储器中删除文档的所有痕迹,因为其中可能会包含敏感信息
only-if-cached 只有当缓存中有副本存在时,客户端才会获取一份副本

注:参见《HTTP权威指南》第194页,表中没有包括兼容性的Progma: no-cache

3、OkHttp3缓存简单使用

OkHttp3中关于缓存的类,我们使用的最多的是:Cache类和ControlCache类,其中前者用于指定缓存的地址和大小,后者用于对缓存进行各种控制,ControlCache又有其构建者类Builder。

先利用Cache类定义缓存地址和缓存最大尺寸:

long maxCacheSize = 100 * 1024 * 1024;
Cache cache = new Cache(
        new File("E:/Soft_Develop/iMooc/NetworkFrameDesign/Test"),
        maxCacheSize);

然后将其注入到OkHttpClient实例中:

OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

接着,构建我们的请求报文Request,我们使用凤凰网首页的网址,因为凤凰网首页的服务器支持缓存:

Request request = new Request.Builder()
                .url("http://www.ifeng.com")
                .build();

为了验证缓存是有效的,我们进行两次拉取,分别使用Response类的networkResponse()方法和cacheResponse()方法来查看从网络或缓存中读取到的内容情况:

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");
Response response2 = client.newCall(request).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());

控制台输出的响应如下:

image.png

由结果可知,由于在第一次请求时,缓存地址中还没有响应缓存,因此是从网络拉取数据,而第二次因为缓存地址中已有数据,因此是从缓存中拉取数据。

需要注意的是,在执行完请求之后,ResponseBody需要关闭(Bodystring()方法内嵌了关闭功能),否则缓存将不会如期生效,比如我们将其中关闭主体的两行去掉,那么结果将不会从缓存中读取:

image.png

我们到缓存地址中查看缓存文件,发现有三个:

image.png

我们查看d0dae*.0这个文件可:

image.png

它的内容我们是熟悉的,它里面包含了几个与缓存相关的字段,该文件所包含的就是缓存中存储的响应头。而主体就是对应的d0dae*.1这个文件了
journal文件这里暂且不管。

4、OkHttp3控制缓存

上一节说到,OkHttp3控制缓存的类为CacheControl。这一小节具体讲讲怎么使用该类。
要对缓存进行控制,我们需要在创建Request实例的时候就为其注入相应的缓存控制机制。这个注入是通过RequestcacheControl(CacheControl )方法实现的。例如,加入我不想存储缓存,而是直接从服务器拉取,并且不保存缓存。要实现这样的功能,HTTP是通过首部字段Cache-Control: no-store实现的。使用OkHttp3我们只需要在构造Request实例时按如下方式增加一行代码:

Request request = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noStore().build())
        .build();

浏览CacheControl.Builder类的方法,我们发现和第2节的表格高度对应:

image.png

针对这些方法,OkHttp3 API也给了我们这样两条建议:

(1)强制从网络获取资源

  • 如果想要跳过缓存直接从网络中获取资源,可以通过noCache()方法;
  • 如果需要每次请求都进行再验证环节,如果验证通过还是使用缓存,那么可以使用maxAge(0, TimeUnit.SECONDS)来构建CacheControl

(2)强制从缓存获取网络资源

  • 可以使用onlyIfCached()方法,使用该方法,如果缓存中没有时,将返回504 Unsatisfiable Request
  • 也可以采用maxStale(365, TimeUnit.DAYS),这样我们就使用了一个很长的缓存放松时间。

5、no-cache, no-store以及max-age=0辨析

上一节,我们使用了no-cache, no-storemax-age=0来强制和服务器进行沟通,但他们之间是有区别的,尤其是no-cachemax-age=0之间,更是令我费解了好一阵子。这里我们就来辨析他们的不同,好在这里我们通过OkHttp3可以很自由的变动我们的请求首部字段,也更容易观察现象。
按照上一章的理解(我参照的是《HTTP权威指南》和RFC7234),no-cache的意思是,在每次请求缓存时需要经过再验证。而OkHttp3给我们的建议中却说no-cache是直接从服务器拉取数据,这样便和再验证机制没什么关系了。
我原先来理解是,no-cache的作用和max-age=0的作用是一样的。但是事实验证不是如此。
我们通过如下实验来说明他们究竟说明的是什么?

5.1、no-cache和max-age=0的区别

首先,我们创建两个URL一样的请求,但第二个请求我们使用no-cache,代码如下:

Request request = new Request.Builder()
                .url("http://www.ifeng.com")
                .build();

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");

Request request2 = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noCache().build())
        .build();

Response response2 = client.newCall(request2).execute();
// String body2 = response2.body().string();
response.body().close();
System.out.println("network response2 = " + response2.networkResponse());
System.out.println("cache response2 = " + response2.cacheResponse());

结果为:

image.png

可以看出,第二次也是直接从网络读取,而不会从缓存中读取。如果将第二个请求中的noCache()换成maxAge(0, TimeUnit.SECONDS)。结果为:

image.png

可以清楚的看到,这次在第二次请求中,再验证机制起作用了,从服务器返回了304 Not Modified,然后再向缓存发起请求,并从响应返回了副本。
由此可见no-cachemax-age=0区别在于,前者直接从服务器拉取数据,后者使用了再验证机制。
RFC 2616中的一段话为这一结论带来了理论基础:

image.png

用通俗的话来说,max-age=0的功能是刷新no-cache的功能是重新加载。使用chrome浏览器打开www.ifeng.com,然后利用开发者工具来抓取报文。当我们按F5时,请求报文部分如下:

image.png

而当我们按CTRL+F5时,请求报文部分如下:

image.png

因此可以得出结论max-age=0等于F5(刷新)no-cache等于CTRL+F5(重载)

5.2、no-cache和no-store的区别

我们只使用一个Request请求,首先给它指定缓存控制为no-cache

Request request = new Request.Builder()
        .url("http://www.ifeng.com")
        .cacheControl(new CacheControl.Builder().noCache().build())
        .build();

Response response = client.newCall(request).execute();
response.body().close();
// String body2 = response2.body().string();
System.out.println("network response = " + response.networkResponse());
System.out.println("cache response = " + response.cacheResponse());
System.out.println("--------------------");

查看缓存地址中产生了缓存文件,但是当把noCache()换成noStore()之后没有产生缓存文件。

由此可见,max-age=0no-cache的主要区别在于是否进行再验证。而no-cacheno-store的区别在于是否会缓存副本。

6、总结

本文主要从应用层面探讨了OkHttp3使用缓存时的基本用法,还有一些比较弄混的问题。但总体说来,我还是没有太弄清楚RFC 7243和《HTTP权威指南》中关于请求报文中的no-cache中的解释为什么会和再验证有关。如果有人看到这篇文章,并且知道为什么,还请指点一二,万分感激!

参考资料

《HTTP权威指南》
RFC 2616

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

推荐阅读更多精彩内容