在网上看到一篇国外的讲okhttp缓存的文章,感觉写的很好,简明扼要。国内虽然也有很多讲okhttp缓存的文章,有的写的很好,有的则写的比较繁琐。所以我还是把这篇文章大致翻译一下,并结合使用过程中的一些情况以及自己的理解,来谈一下okhttp缓存。原来的文章在这里
先放一张图,这是不考虑离线情况下的okhttp缓存工作流程图:
okhttp的缓存是基于http协议的,也就是,假如服务器返回的http header里表明了是不支持cache的,比如这样
这是某一台服务器返回的response header,如果返回了这样的header的话,那么你就无法使用okhttp的cache了,只能考虑通过拦截器来自己实现。不过我觉得这样很奇怪,cache本身是避免重复请求浪费服务器资源的,所以http协议里对缓存时间,缓存策略都做了详细的规范,诸如Cache-Control,Expires,Last-Modified,max-age等等。如果绕过http协议,自身实现,你如何保证你的缓存没有过期呢?或者换个说法,你如何确定这是服务器希望你使用的缓存呢?
然后接下来文章里是一些Quick questions and answers.,通过这些Q&A,你会对okhttp的缓存有一个很好的了解
1. 如何启用okhttp的缓存?
只需要一句代码就够了:
int cacheSize = 10 * 1024 * 1024; // 10MB
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cache(new Cache(context.getCacheDir(), cacheSize)
2. 如何让okhttp的缓存工作?
答案是Do nothing!okhttp实现http标准协议里的缓存策略。所以你开启缓存之后什么也不用做。这主要取决于后端,只要后端开启了缓存策略,那么就会在response里就会返回相应的header,okhttp会自动解析这些header来使用缓存。举个例子,假如你的服务器有这样的返回:
Date:Wed, 29 Mar 2017 10:54:09 GMT
ETag:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"
Last-Modified:Wed, 29 Mar 2017 03:25:27 GMT
那么下一次okhttp发起相同的请求的时候,会在request里加上这样的header:
If-Modified-Since:Wed, 29 Mar 2017 03:25:27 GMT
If-None-Match:"82ccabc2f791cdd2217922e2f362bb4f:1490757927"
3. 如何使用离线缓存?
如果服务器返回了max-age,并且okhttp也缓存了,那么在离线状态下,缓存将正常工作。如果没有max-age或者max-age过期了,但你仍然希望使用缓存,那么你可以这样构造你的request
new Request.Builder().cacheControl(CacheControl.FORCE_CACHE)
然后okhttp的缓存策略就变成了这样:
如果你一定要在没有网的情况下,使用缓存,那么你可以这样写
public class ForceCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (!NetworkUtils.internetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}
okHttpClient.addInterceptor(new ForceCacheInterceptor());
注意,这里使用的是Interceptor 而不是network interceptor,如果使用network interceptor,则没有机会触发它。
上面这些是文章里的原文,我谈谈自己的看法。像这样通过拦截器在没有网的时候使用缓存数据,不失为一个办法,很多讲okhttp缓存的文章里都会见到这样一个拦截器。但我个人认为其实没什么必要,因为这里你首先要曾经获取过缓存,那么当你使用FORCE_CACHE才会生效。其次,使用一个不知道什么时候的缓存,真的有必要吗?
4. 使用okhttp的缓存策略来存储数据是一个good idea吗?我们只需要设置FORCE_CACHE,然后我们就总能获取到存储的数据
It’s a bad idea。这根本是两个概念,应该区别对待。文章在这里提到,token应该被存储,而诸如新闻之类的东西才应该被缓存。文章的意思是缓存和存储是完全不同的两个概念,其实这也很好理解。停一下,喝杯咖啡思考一下~
5. 如何确定我们是否需要服务器来校验缓存?
使用max-age来让okhttp知道我们要保留这个缓存多长时间
For example, you are refreshing your Facebook setting page which has a list of settings. So, that list should stay unchanged for quite sometimes. So instead of return the same response every time, the server can just say max-age=3600 and for all the subsequence requests in the next 1 hour (3600 seconds), the client can just use the local cached data.
上面这一段是文章中的原话,大致是说,例如脸书设置页面的一些list,短时间内不会更改的,可以考虑设置一个小时的cache 时间。但这是2017年的文章了,我在最新的脸书的设置页面里,没见到类似的需要服务器返回的列表页。总之,通过这个例子理解max-age的用处就可以了。
6. 服务器是如何确定客户端是否应该使用缓存的?
通过以下几步:
1.客户端在request里带上timestamp或者Etag这样的header
2.服务器检查在上一次请求和这一次请求里,数据是否有变化
3.如果什么也没改变,那么服务器可以返回一些特殊码,例如http协议里标准的304代码,表示未修改。同时,不再次返回完整的response。这样,可以节省一些带宽和流量。当然,你返回200也可以,带上Cache-Control,,Content-Location就行。
One example is the Gmail app, because the client has no way to know if there are new emails every time the user refreshes, so it can’t just simply use the cached data. What it always does is to send out the last Etag to tell the server what was the last time the user checked their inbox. Then if there is no new email, the server just tells the client to use its cache to display the same email list.
上面这段话也是原文里的,我就不一句一句翻译了。大致是说,以gmail为例,可以通过每次请求时带上Etag,然后服务器就知道该不该返回新的数据给客户端,客户端也知道该不该使用缓存了。