OKHTTP之缓存配置详解

本文就是讲解在OKHTTP中如何配置缓存

HTTP协议中缓存相关

为了更好的讲解OKHTTP怎么设置缓存,我们追根溯源先从浏览器的缓存说起,这样后面的OKHTTP缓存内容自然更加好理解。

我这部分内容也是经网络上查阅,这一篇写得很详细:

浏览器 HTTP 协议缓存机制详解

https://my.oschina.net/leejun2005/blog/369148

以下内容基本出自于此文章。

缓存分类

http请求有服务端和客户端之分。因此缓存也可以分为两个类型服务端侧和客户端侧。

服务端缓存

常见的服务端有Ngix和Apache。服务端缓存又分为代理服务器缓存和反向代理服务器缓存。常见的CDN就是服务器缓存。这个好理解,当浏览器重复访问一张图片地址时,CDN会判断这个请求有没有缓存,如果有的话就直接返回这个缓存的请求回复,而不再需要让请求到达真正的服务地址,这么做的目的是减轻服务端的运算压力。

客户端缓存

客户端主要指浏览器(如IE、Chrome等),当然包括我们的OKHTTPClient.客户端第一次请求网络时,服务器返回回复信息。如果数据正常的话,客户端缓存在本地的缓存目录。当客户端再次访问同一个地址时,客户端会检测本地有没有缓存,如果有缓存的话,数据是有没有过期,如果没有过期的话则直接运用缓存内容。

而我们讲的就是客户端的缓存。

缓存中重要的概念

Cache-Control

Cache-Control是什么呢?先别急,我们先用观察一个结果,用Fiddler监听浏览器访问http://blog.csdn.net/briblue。如下图:

然后,查看服务端返回来的信息如下:

可以看到头信息中有这么一行:

Cache-Control:private

Cache-control是由服务器返回的 Response 中添加的头信息,它的目的是告诉客户端是要从本地读取缓存还是直接从服务器摘取消息。它有不同的值,每一个值有不同的作用。

max-age:这个参数告诉浏览器将页面缓存多长时间,超过这个时间后才再次向服务器发起请求检查页面是否有更新。对于静态的页面,比如图片、CSS、Javascript,一般都不大变更,因此通常我们将存储这些内容的时间设置为较长的时间,这样浏览器会不会向浏览器反复发起请求,也不会去检查是否更新了。

s-maxage:这个参数告诉缓存服务器(proxy,如Squid)的缓存页面的时间。如果不单独指定,缓存服务器将使用max-age。对于动态内容(比如文档的查看页面),我们可告诉浏览器很快就过时了(max-age=0),并告诉缓存服务器(Squid)保留内容一段时间(比如,s-maxage=7200)。一旦我们更新文档,我们将告诉Squid清除老的缓存版本。

must-revalidate:这告诉浏览器,一旦缓存的内容过期,一定要向服务器询问是否有新版本。

proxy-revalidate:proxy上的缓存一旦过期,一定要向服务器询问是否有新版本。

no-cache:不做缓存。

no-store:数据不在硬盘中临时保存,这对需要保密的内容比较重要。

public:告诉缓存服务器, 即便是对于不该缓存的内容也缓存起来,比如当用户已经认证的时候。所有的静态内容(图片、Javascript、CSS等)应该是public的。

private:告诉proxy不要缓存,但是浏览器可使用private cache进行缓存。一般登录后的个性化页面是private的。

no-transform: 告诉proxy不进行转换,比如告诉手机浏览器不要下载某些图片。

max-stale:指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

我们上面的例子是 Cache-Control:private。说明服务器希望客户端不要缓存消息,但是可以进行 private cache 方法进行缓存。这是因为http://blog.csdn.net/briblue是我的博客页面,与用户系统相关,所以为了安全起见,建议用 private cache 的方式缓存。

在OKHttp开发中我们常见到的有下面几个:

max-age

no-cache

max-stale

expires

expires 的效果等同于 Cache-Control,不过它是Http 1.0的内容,它的作用是告诉浏览器缓存的过期时间,在此时间内浏览器不需要直接访问服务器地址直接用缓存内容就好了。

expires 最大的问题在于如果服务器时间和本地浏览器相差过大的问题。那样误差就很大。所以基本上用Cache-Control:max-age=多少秒的形式代替。

Last-Modified/If-Modified-Since

这个需要配合Cache-Control使用

Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。

If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。

Etag/If-None-Match

这个也需要配合Cache-Control使用

Etag 对应请求的资源在服务器中的唯一标识(具体规则由服务器决定),比如一张图片,它在服务器中的标识为 ETag: W/”ACXbWXd1n0CGMtAd65PcoA==”。

If-None-Match 如果浏览器在 Cache-Control:max-age=60 设置的时间超时后,发现消息头中还设置了Etag值。然后,浏览器会再次向服务器请求数据并添加 In-None-Match 消息头,它的值就是之前Etag值。服务器通过Etag来定位资源文件,根据它是否更新的情况给浏览器返回200或者是304。

Etag机制比Last-Modified精确度更高,如果两者同时设置的话,Etag优先级更高。

Pragma

Pragma 头域用来包含实现特定的指令,最常用的是 Pragma:no-cache。

在HTTP/1.1协议中,它的含义和 Cache- Control:no-cache 相同。

以上是Http中关于缓存的相关信息。接下来我们进入主题,如何配置OkHttp的缓存。

OKHTTP之Cache

OKHTTP如果要设置缓存,首要的条件就是设置一个缓存文件夹,在Android中为了安全起见,一般设置为私密数据空间。通过 getExternalCacheDir() 获取。如然后通过调用 OKHttpClient.Builder 中的 cache() 方法。如下面代码所示:

设置好Cache我们就可以正常访问了。我们可以通过获取到的Response对象拿到它正常的消息和缓存的消息。

Response的消息有两种类型,CacheResponse 和 NetworkResponse。CacheResponse代表从缓存取到的消息,NetworkResponse 代表直接从服务端返回的消息。示例代码如下:

我们在上面的代码中,用同一个url地址分别进行了两次网络访问,然后分别用Log打印它们的信息。

打印的结果非常有意思是一个机器人一个 Okhttp 的字符串。打印的结果主要说明了一个现象,第一次访问的时候,Response 的消息是 NetworkResponse 消息,此时 CacheResponse 的值为Null。而第二次访问的时候 Response 是 CahceResponse,而此时 NetworkResponse 为空。也就说明了上面的示例代码能够进行网络请求的缓存。

那么 OKHTTP 中的缓存就这么点内容吗?到此为至吗?显然不是。本篇文章开头讲了大段的Http协议中的相关知识点,貌似它们还没有出现。

其实控制缓存的消息头往往是服务端返回的信息中添加的如”Cache-Control:max-age=60”。所以,会有两种情况:

客户端和服务端开发能够很好沟通,按照达成一致的协议,服务端按照规定添加缓存相关的消息头。

客户端与服务端的开发根本就不是同一家公司,没有办法也不可能要求服务端按照客户端的意愿进行开发。

第一种办法当然很好,只要服务器在返回消息的时候添加好Cache-Control相关的消息便好。

第二种情况,就很麻烦,你真的无法左右别人的行为。怎么办呢?好在OKHTTP能够很轻易地处理这种情况。那就是定义一个拦截器,人为地添加Response中的消息头,然后再传递给用户,这样用户拿到的Response就有了我们理想当中的消息头Headers,从而达到控制缓存的意图,正所谓移花接木。

缓存之拦截器

因为拦截器可以拿到 Request 和 Response,所以可以轻而易举地加工这些东西。在这里我们人为地添加 Cache-Control 消息头。

定义好拦截器中后,我们可以添加到OKHttpClient中了。

代码后面部分有省略。主要通过在OkHttpClient.Builder()中addNetworkInterceptor()中添加。而这样也挺简单的,就几步完成了缓存代码。

拦截器进行缓存的缺点

网上有人说用拦截器进行缓存是野路子,是HOOK行为。这个我不大同意,前面我有分析过情况,如果客户端能够同服务端一起协商开发,当然以服务器控制的缓存消息头为准,但问题在于你没法这样做。所以,能够解决问题才是最实在的。

好了,回到正题。用拦截器控制缓存有什么不好的地方呢?我们先看看下面的情况。

1.网络访问请求的资源是文本信息,如新闻列表,这类信息经常变动,一天更新好几次,它们用的缓存时间应该就很短。

2.网络访问请求的资源是图片或者视频,它们变动很少,或者是长期不变动,那么它们用的缓存时间就应该很长。

那么,问题来了。因为OKHTTP开发建议是同一个APP,用同一个OKHTTPCLIENT对象这是为了只有一个缓存文件访问入口。这个很容易理解,单例模式嘛。但是问题拦截器是在OKHttpClient.Builder当中添加的。如果在拦截器中定义缓存的方法会导致图片的缓存和新闻列表的缓存时间是一样的,这显然是不合理的,这属于一刀切,就像这两天专家说的要把年收入12万元的人群划分为高收入人群而不区别北上广深的房价物价情况。真实的情况不应该是图片请求有它的缓存时间,新闻列表请求有它的缓存时间,应该是每一个Request有它的缓存时间。

那么,有解决的方案吗? 有的,okhttp官方有建议的方法。

okhttp官方文档建议缓存方法

okhttp中建议用 CacheControl 这个类来进行缓存策略的制定。它内部有两个很重要的静态实例。

我们看到 FORCE_NETWORK 常量 用来强制使用网络请求。FORCE_CACHE 只取本地的缓存。它们本身都是 CacheControl 对象,由内部的Buidler对象构造。下面我们来看看CacheControl.Builder

CacheControl.Builder

它有如下方法:

知道了 CacheControl 的相关信息,那么它怎么使用呢?不同于拦截器设置缓存, CacheControl 是针对 Request 的,所以它可以针对每个请求设置不同的缓存策略。比如图片和新闻列表。下面代码展示如何用 CacheControl 设置一个60秒的超时时间。

强制使用缓存

前面有讲CacheControl.FORCE_CACHE这个常量。

它内部其实就是调用 onlyIfCached() 和 maxStale 方法。它的使用方法为:

但是如前面后提到的,如果缓存不符合条件会返回504.这个时候我们要根据情况再进行编码,如缓存不行就再进行一次网络请求。

不使用缓存

前面也有讲CacheControl.FORCE_NETWORK这个常量。

publicstaticfinalCacheControlFORCE_NETWORK=newBuilder().noCache().build();

它的内部其实是调用noCache()方法,也就是不缓存的意思。它的使用方法为:

还有一种情况将maxAge设置为0,也不会取缓存,直接走网络。

总结

本文其实内容不多,前面讲了很多http协议下的缓存机制,我认为是值得的,知道了Cache-Control这些定义,才能更好的懂得OKHTTP中的缓存设置。能够明白为什么它要这样做,为什么它可以这样做。

最后归纳下要点:

http协议下Cache-Control等消息头的作用

okhttp如何用拦截器添加Cache-Control消息头进行缓存定制

okhttp如何用CacheControl进行缓存的控制。

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

推荐阅读更多精彩内容

  • 前言 在Android开发中我们经常要进行各种网络访问,比如查看各类新闻、查看各种图片。但有一种情形就是我们每次重...
    SnowDragonYY阅读 6,214评论 2 11
  • 1.OkHttp源码解析(一):OKHttp初阶 2 OkHttp源码解析(二):OkHttp连接的"前戏"——H...
    隔壁老李头阅读 8,282评论 12 43
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,509评论 0 6
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,490评论 18 139
  • 大家好,之前我们讲解了Okhttp网络数据请求相关的内容,这一节我们讲讲数据缓存的处理。本节按以下内容讲解Okht...
    Ihesong阅读 10,248评论 6 26