File cacheFile = new File(getExternalCacheDir().toString(),"cache");
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");
int cacheSize = 10 * 1024 * 1024;
final Cache cache = new Cache(cacheFile,cacheSize);
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
String url = "http://publicobject.com/helloworld.txt";
Request request = new Request.Builder()
.url(url)
.build();
Call call1 = client.newCall(request);
Response response1 = null;
try {
response1 = call1.execute();
Log.i(TAG, "testCache: response1 :"+response1.body().string());
Log.i(TAG, "testCache: response1 cache :"+response1.cacheResponse());
Log.i(TAG, "testCache: response1 network :"+response1.networkResponse());
response1.body().close();
} catch (IOException e) {
e.printStackTrace();
}
Call call12 = client.newCall(request);
try {
Response response2 = call12.execute();
Log.i(TAG, "testCache: response2 :"+response2.body().string());
Log.i(TAG, "testCache: response2 cache :"+response2.cacheResponse());
Log.i(TAG, "testCache: response2 network :"+response2.networkResponse());
Log.i(TAG, "testCache: response1 equals response2:"+response2.equals(response1));
response2.body().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
打印的结果非常有意思是一个机器人和一个Okhttp的字符串。打印的结果主要说明了一个现象,第一次访问的时候,Response的消息是NetworkResponse消息,此时CacheResponse的值为Null.而第二次访问的时候Response是CahceResponse,而此时NetworkResponse为空。也就说明了上面的示例代码能够进行网络请求的缓存。
那么OKHTTP中的缓存就这么点内容吗?到此为至吗?显然不是。本篇文章开头讲了大段的Http协议中的相关知识点,貌似它们还没有出现。
其实控制缓存的消息头往往是服务端返回的信息中添加的如”Cache-Control:max-age=60”。所以,会有两种情况。 1. 客户端和服务端开发能够很好沟通,按照达成一致的协议,服务端按照规定添加缓存相关的消息头。 2. 客户端与服务端的开发根本就不是同一家公司,没有办法也不可能要求服务端按照客户端的意愿进行开发。
第一种办法当然很好,只要服务器在返回消息的时候添加好Cache-Control相关的消息便好。
第二种情况,就很麻烦,你真的无法左右别人的行为。怎么办呢?好在OKHTTP能够很轻易地处理这种情况。那就是定义一个拦截器,人为地添加Response中的消息头,然后再传递给用户,这样用户拿到的Response就有了我们理想当中的消息头Headers,从而达到控制缓存的意图,正所谓移花接木。
缓存之拦截器
因为拦截器可以拿到Request和Response,所以可以轻而易举地加工这些东西。在这里我们人为地添加Cache-Control消息头。
class CacheInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Response originRespOnse= chain.proceed(chain.request());
return originResponse.newBuilder().removeHeader("pragma")
.header("Cache-Control","max-age=60").build();
}
}
定义好拦截器中后,我们可以添加到OKHttpClient中了。
private void testCacheInterceptor(){
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
int cacheSize = 10 * 1024 * 1024;
final Cache cache = new Cache(cacheFile,cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new CacheInterceptor())
.cache(cache)
.build();
.......
}
代码后面部分有省略。主要通过在OkHttpClient.Builder()中addNetworkInterceptor()中添加。而这样也挺简单的,就几步完成了缓存代码。
拦截器进行缓存的缺点
网上有人说用拦截器进行缓存是野路子,是HOOK行为。这个我不大同意,前面我有分析过情况,如果客户端能够同服务端一起协商开发,当然以服务器控制的缓存消息头为准,但问题在于你没法这样做。所以,能够解决问题才是最实在的。
好了,回到正题。用拦截器控制缓存有什么不好的地方呢?我们先看看下面的情况。
1. 网络访问请求的资源是文本信息,如新闻列表,这类信息经常变动,一天更新好几次,它们用的缓存时间应该就很短。
2. 网络访问请求的资源是图片或者视频,它们变动很少,或者是长期不变动,那么它们用的缓存时间就应该很长。
那么,问题来了。
因为OKHTTP开发建议是同一个APP,用同一个OKHTTPCLIENT对象这是为了只有一个缓存文件访问入口。这个很容易理解,单例模式嘛。但是问题拦截器是在OKHttpClient.Builder当中添加的。如果在拦截器中定义缓存的方法会导致图片的缓存和新闻列表的缓存时间是一样的,这显然是不合理的,这属于一刀切,就像这两天专家说的要把年收入12万元的人群划分为高收入人群而不区别北上广深的房价物价情况。真实的情况不应该是图片请求有它的缓存时间,新闻列表请求有它的缓存时间,应该是每一个Request有它的缓存时间。
那么,有解决的方案吗?
有的,okhttp官方有建议的方法。
okhttp官方文档建议缓存方法
okhttp中建议用CacheControl这个类来进行缓存策略的制定。
它内部有两个很重要的静态实例。
/**强制使用网络请求*/
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
/** * 强制性使用本地缓存,如果本地缓存不满足条件,则会返回code为504 */
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
我们看到FORCE_NETWORK常量用来强制使用网络请求。FORCE_CACHE只取本地的缓存。它们本身都是CacheControl对象,由内部的Buidler对象构造。下面我们来看看CacheControl.Builder
CacheControl.Builder
它有如下方法:
- noCache();
- noStore();
- onlyIfCached();
- noTransform();
- maxAge(10, TimeUnit.MILLISECONDS);
- maxStale(10, TimeUnit.SECONDS);
- minFresh(10, TimeUnit.SECONDS);
知道了CacheControl的相关信息,那么它怎么使用呢?不同于拦截器设置缓存,CacheControl是针对Request的,所以它可以针对每个请求设置不同的缓存策略。比如图片和新闻列表。下面代码展示如何用CacheControl设置一个60秒的超时时间。
private void testCacheControl(){
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
int cacheSize = 10 * 1024 * 1024;
final Cache cache = new Cache(cacheFile,cacheSize);
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
CacheControl cacheCOntrol= new CacheControl.Builder()
.maxAge(60, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("http://blog.csdn.net/briblue")
.cacheControl(cacheControl)
.build();
try {
Response respOnse= client.newCall(request).execute();
response.body().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
强制使用缓存
前面有讲CacheControl.FORCE_CACHE这个常量。
public static final CacheControl FORCE_CACHE = new Builder()
.onlyIfCached()
.maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
.build();
它内部其实就是调用onlyIfCached()和maxStale方法。
它的使用方法为
Request request = new Request.Builder()
.url("http://blog.csdn.net/briblue")
.cacheControl(Cache.FORCE_CACHE)
.build();
但是如前面后提到的,如果缓存不符合条件会返回504.这个时候我们要根据情况再进行编码,如缓存不行就再进行一次网络请求。
Response forceCacheRespOnse= client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
} else {
}
不使用缓存
前面也有讲CacheControl.FORCE_NETWORK这个常量。
public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
它的内部其实是调用noCache()方法,也就是不缓存的意思。
它的使用方法为
Request request = new Request.Builder()
.url("http://blog.csdn.net/briblue")
.cacheControl(Cache.FORCE_NETWORK)
.build();
还有一种情况将maxAge设置为0,也不会取缓存,直接走网络。
Request request = new Request.Builder()
.url("http://blog.csdn.net/briblue")
.cacheControl(new CacheControl.Builder()
.maxAge(0, TimeUnit.SECONDS))
.build();
总结
本文其实内容不多,前面讲了很多http协议下的缓存机制,我认为是值得的,知道了Cache-Control这些定义,才能更好的懂得OKHTTP中的缓存设置。能够明白为什么它要这样做,为什么它可以这样做。
最后归纳下要点
- http协议下Cache-Control等消息头的作用
- okhttp如何用拦截器添加Cache-Control消息头进行缓存定制
- okhttp如何用CacheControl进行缓存的控制。