OkHttp3之缓存应用

来源:互联网 发布:卡尔曼滤波算法应用 编辑:程序博客网 时间:2024/05/22 10:56

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