OkHttp实现缓存
需求
在有网的情况下,正常进行网络请求,然后把响应缓存到本地;在无网的情况下,从本地拿到缓存,返回给调用方。
限制
不能改变服务器的API,服务器的API没有Cache-Control字段。
思路
利用OkHttp的拦截器实现。
OkHttp请求过程:OkHttp的缓存机制(CacheInterceptor)会自动判断我们提交的Request中的Cache-Control头:如果是only-if-cache(FORCE_CACHE),则只能从缓存中获取,不能进行网络请求,如果获取缓存失败,则返回一个504的错误响应码;如果是no-cache则只从网络中获取。
OkHttp响应过程:当正常的网络请求返回之后,CacheInterceptor会自动判断Response的Cache-Control头,如果是only-if-cache,则会缓存到本地;如果是no-cache,则不缓存。
所以,我们可以在响应返回到CacheInterceptor之前拦截Response,强制加上Cache-Control: only-if-cache,保存缓存;然后在请求发出到CacheInterceptor之前拦截Resquest,判断当前网络状态,如果无网,则强制加上Cache-Control: only-if-cache,让请求从缓存中直接获取。
关于拦截器的具体源码分析可以看我的另一篇博客:OkHttp源码解析
实现
拦截器
我们先来看看拦截器的责任链工作模式:
我们提交的请求首先会经过我们自定义的Interceptors,然后经过缓存(CacheInterceptor)处理,接着经过也是我们自定义的NetworkInterceptors,最后才交给CallServerInterceptor传输到TCP流。
如何实现一个拦截器?
//示例
static class DemoInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Request req = chain.request(); //获得请求
Response res = chain.process(req); //交给责任链下一环执行,最后回传一个响应
return res; //返回响应给上一层
}
}
编写请求拦截器
先写一个请求拦截器,在请求发出去之前检查网络,如果无网,则要求使用缓存:
public class Util {
//判断网络连接状态
public static boolean isNetworkConnected() {
ConnectivityManager connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isAvailable();
}
}
static class RequestCacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder(); //在原来的request的基础上修改
if (! Util.isNetworkConnected()) {
//无网下强制缓存
builder.cacheControl(CacheControl.FORCE_CACHE); //等同于添加only-if-cache
}
Request newRequest = builder.build();
return chain.proceed(newRequest);
}
}
编写响应拦截器
再写一个响应拦截器,强制要求缓存:
static class ResponseCacheInterceptor implements Interceptor{
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request()).newBuilder()
.removeHeader("Pragma") //移除影响
.removeHeader("Cache-Control") //移除影响
.addHeader("Cache-Control", CacheControl.FORCE_CACHE.toString()).build();
return response;
}
}
添加到OkHttpClient
最后添加这些拦截器到OkHttpClient上:
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(Util.getContext().getExternalCacheDir(), 50 * 8 * 1024 * 1024)) //设置缓存目录大小50MB
.addInterceptor(new RequestCacheInterceptor()) //在用户端添加请求拦截器
.addNetworkInterceptor(new ResponseCacheInterceptor()) //在网络端添加响应拦截器(注意和用户端的区别)
.build();