本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
前言
在Android开发中我们经常要进行各种网络访问,比如查看各类新闻、查看各种图片。但有一种情形就是我们每次重复发送的网络请求其实返回的内容都是一样的。比如一个电影类APP,每一次向服务器申请某个电影的相关信息,如封面、简介、演员表等等,它们的信息都是一样的。显然,这样有点浪费资源,最主要的是这些重复的请求产生了没有必要的流量。流量、流量、流量!!!重要的事情说三遍!刚开始工作的我也不懂,后来才发现,流量是要付费的,而且超贵,公司那么小,一个月要支付宽带运营商巨额的流量费用。所以领导们都想方设法地要节省带宽。
其实这在整个软件开发中随时可见,解决的方法就是把重复请求的数据缓存在本地,并设置超时时间,在规定时间内,客户端不再向远程请求数据,而是直接从本地缓存中取数据。这样一来提高了响应速度,二来节省了网络带宽(也就是节省了钱)。
本文就是讲解在OKHTTP中如何配置缓存。
HTTP协议中缓存相关
为了更好的讲解OKHTTP怎么设置缓存,我们追根溯源先从浏览器的缓存说起,这样后面的OKHTTP缓存内容自然更加好理解。
我这部分内容也是经网络上查阅,这一篇写得很详细浏览器 HTTP 协议缓存机制详解。以下内容基本出自于此文章。
缓存分类
http请求有服务端和客户端之分。因此缓存也可以分为两个类型服务端侧和客户端侧。
服务端侧缓存
常见的服务端有Ngix和Apache。服务端缓存又分为代理服务器缓存和反向代理服务器缓存。常见的CDN就是服务器缓存。这个好理解,当浏览器重复访问一张图片地址时,CDN会判断这个请求有没有缓存,如果有的话就直接返回这个缓存的请求回复,而不再需要让请求到达真正的服务地址,这么做的目的是减轻服务端的运算压力。
客户端侧缓存
客户端主要指浏览器(如IE、Chrome等),当然包括我们的OKHTTPClient.客户端第一次请求网络时,服务器返回回复信息。如果数据正常的话,客户端缓存在本地的缓存目录。当客户端再次访问同一个地址时,客户端会检测本地有没有缓存,如果有缓存的话,数据是有没有过期,如果没有过期的话则直接运用缓存内容。
而我们讲的就是客户端的缓存。
缓存中重要的概念
Cache-Control
Cache-Control是什么呢?先别急,
我们先用观察一个结果,用Fiddler监听浏览器访问http://blog.csdn.net/briblue。如下图:
然后,查看服务端返回来的信息如下:
HTTP/1.1 200 OK
Server: openresty
Date: Mon, 24 Oct 2016 09:00:34 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
X-Powered-By: PHP 5.4.28
Content-Encoding: gzip
可以看到头信息中有这么一行:
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()方法。如下面代码所示:
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M
int cacheSize = 10 * 1024 * 1024;
//创建缓存对象
Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
设置好Cache我们就可以正常访问了。我们可以通过获取到的Response对象拿到它正常的消息和缓存的消息。
Response的消息有两种类型,CacheResponse和NetworkResponse。CacheResponse代表从缓存取到的消息,NetworkResponse代表直接从服务端返回的消息。示例代码如下:
private void testCache(){
//缓存文件夹
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//缓存大小为10M