在Http协议中,缓存的控制是经过首部的Cache-Control来控制,经过对Cache-Control进行设置,便可实现不一样的缓存策略。html
Cache-Control和其余的首部字段同样,使用key:value结构,同时value可有多个值, 值之间以,分隔(具体参考HTTP详解)。Cache-Control是一个通用首部字段,在Http请求报文中可以使用,也可在应答报文中使用。java
public: 可向任一方提供缓存数据;缓存
private: 只向指定用户提供缓存数据;服务器
no-cache: 缓存前需确认其有效性;markdown
no-store: 不缓存请求或响应的任何内容;cookie
max-age: 表示缓存的最大时间,在此时间范围内,访问该资源时,直接返回缓存数据。不须要对资源的有效性进行确认;网络
must-revalidate: 访问缓存数据时,须要先向源服务器确认缓存数据是否有效,如没法验证其有效性,则需返回504。须要注意的是:若是使用此值,则max-stale将无效。ide
更详细内容可参考:Http首部字段定义oop
了解了HTTP的理论知识,后面咱们对OkHttp中的缓存进行简单的介绍。post
OkHttp默认对Http缓存进行了支持,只要服务端返回的Response中含有缓存策略,OkHttp就会经过CacheInterceptor拦截器对其进行缓存。可是OkHttp默认状况下构造的HTTP请求中并无加Cache-Control,即使服务器支持了,咱们仍是不能正常使用缓存数据。因此须要对OkHttp的缓存过程进行干预,使其知足咱们的需求。
OkHttp的优雅之处就在于使用了责任链模式,将请求-应答过程当中的每一步都经过一个拦截器来实现,并对此过程的头部和尾部都提供了扩展,这也为咱们干预缓存过程提供了可能。因此在实现缓存以前,咱们须要对OkHttp对拦截器的处理过程有个大概的了解。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest, this, eventListener);
return chain.proceed(originalRequest);
}
复制代码
以上代码就是整个拦截器的处理过程,具体的流程可参考源码,这里咱们只说一下基本的流程:发起请求时,会按interceptors中加入的顺序依次执行,返回Response时按照逆序执行:
自定义拦截器 <-> 内置拦截器(retryAndFollowUpInterceptor...ConnectInterceptor)
<-> 网络拦截器 <-> CallServerInterceptor
复制代码
其中CallServerInterceptor就是负责发送请求与接收应答的拦截器。因为咱们关注的只是缓存,因此只考虑内置拦截器中的CacheInterceptor。那么流程可简化为:
Request <-> 自定义拦截器 <-> CacheInterceptor <-> 网络拦截器 <-> Response
复制代码
从这个流程能够看出,若是服务端返回的Response中没有Cache-Control, 那么咱们可经过添加网络拦截器来实现。一样,在访问缓存数据时,咱们可经过添加自定义拦截器来实现。
在开始添加缓存策略以前,咱们先了解一个完整的缓存策略:
总体来讲,在有网络的状况下,使用缓存仍是比较复杂,这里咱们经过简化版的缓存策略(有网络时访问服务器,无网络时返回缓存数据)来演示OkHttp使用缓存的过程。
首先,咱们经过定义一个网络拦截器来为Response添加缓存策略:
public class HttpCacheInterceptor implements Interceptor {
private Context context;
public HttpCacheInterceptor(Context context) {
this.context = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(chain.request()).newBuilder()
.request(newRequest)
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + 1)
.build();
}
}
复制代码
其次,经过自定义拦截器设置Request使用缓存的策略:
public class BaseInterceptor implements Interceptor {
private Context mContext;
public BaseInterceptor(Context context) {
this.mContext = context;
}
@Override
public Response intercept(Chain chain) throws IOException {
if (NetworkUtil.isConnected(mContext)) {
return chain.proceed(chain.request());
} else { // 若是没有网络,则返回缓存未过时一个月的数据
Request newRequest = chain.request().newBuilder()
.removeHeader("Pragma")
.header("Cache-Control", "only-if-cached, max-stale=" + 30 * 24 * 60 * 60);
return chain.proceed(newRequest);
}
}
}
复制代码
Pragma是Http/1.1以前版本遗留的字段,用于作版本兼容,但不一样的平台对此有不一样的实现,因此在使用缓存策略时须要将其屏蔽,避免对缓存策略形成影响。
将对修改Request和Response缓存策略的拦截器应用于OkHttp:
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new BaseInterceptor(context))
.addNetworkInterceptor(new HttpCacheInterceptor(context))
.cache(new Cache(context.getCacheDir(), 20 * 1024 * 1024)) // 设置缓存路径和缓存容量
.build();
复制代码
接下来就能够在无网络的状况下愉快地使用缓存数据了。
若是以为OkHttp的缓存太复杂,想本身来缓存数据怎么办呢?有两种方案来实现:
这种方案首先须要考虑应使用普通的拦截器仍是网络拦截器,上面咱们已经了解了整个请求过程当中拦截器的执行顺序,须要注意的是:在无网络的状况下,请求在执行到CacheIntercepter,若是没有缓存数据,将会直接返回,并不会执行到自定义的网络拦截器中,因此不适合在网络拦截器中缓存数据。那么咱们可经过自定义普通拦截器来实现,基本的过程以下:
@Override // BaseInterceptor.java
public Response intercept(Chain chain) throws IOException {
Response response = null;
if (NetworkUtil.isConnected(mContext)) {
response = chain.proceed(newRequest);
saveCacheData(response); // 保存缓存数据
} else { // 不执行chain.proceed会打断责任链,即后面的拦截器不会被执行
response = getCacheData(chain.request().url()); // 获取缓存数据
}
return response;
}
复制代码
OkHttp: 使用这种方案你良心不会痛吗?
这种方案能够说摒弃了OkHttp扩展拦截器这一强大的功能,直接与请求和应答进行交互,基本的过程以下:
Request request = new Request.Builder()
.url(realUrl)
.build();
if (NetworkUtil.isConnected()) {
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
// 返回缓存数据
}
@Override
public void onResponse(Response response) throws IOException {
// 1. 缓存数据
// 2. 返回请求结果
}
});
} else {
// 返回缓存数据
}
复制代码
这两种方案都抛弃了OkHttp本身实现的缓存策略,因此更加灵活,尤为是监听OkHttp请求过程这种方法。但也都有一个很大的缺点:须要实现一个缓存模块。在开发中具体使用哪一种缓存策略,根据已有代码模块和需求衡量便可。
对Response的缓存策略进行修改的拦截器必定要应用于网络拦截器,不然没法缓存数据,由于在Response返回的过程当中,普通的拦截器在内置的CacheInterceptor以后执行;
修改Response的Cache-Control时,max-Age不能太大,不然你将在指定的max-Age时间内访问的始终是缓存数据(即使是有网的状况下);
实际的开发过程当中,咱们在网络请求中会添加一些公共参数,对于一些可变的公共参数,在缓存数据和访问缓存数据的过程当中须要删除,好比网络类型,有网络时其值为Wifi或4G等,无网络时可能为none, 这时访问缓存时就会因url不一致致使访问缓存失败。
// BaseInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
// 添加公共参数
HttpUrl.Builder urlBuilder = chain.request().url().newBuilder()
.addQueryParameter("a", "a")
.addQueryParameter("b", "b");
Request.Builder requestBuilder = chain.request().newBuilder();
if (NetworkUtil.isConnected(mContext)) {
urlBuilder.addQueryParameter("network", NetworkUtil.getNetwokType(mContext));
} else { // 无网络时不添加可变的公共参数
requestBuilder.removeHeader("Pragma")
.header("Cache-Control", "only-if-cached, max-stale=" + 30 * 24 * 60 * 60);
}
Request newRequest = requestBuilder
.url(urlBuilder.build())
.build();
return chain.proceed(newRequest);
}
// HttpCacheInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
HttpUrl newUrl = chain.request().url().newBuilder()
.removeAllQueryParameters("network")
.build(); // 缓存数据前删除可变的公共参数
Request newRequest = chain.request().newBuilder()
.url(newUrl)
.build();
return response.newBuilder()
.request(newRequest)
.removeHeader("Pragma")
.header("Cache-Control", "public, max-age=" + 1)
.build();
}
复制代码