内容简介:OkHttp缓存使用指南
在Http协议中,缓存的控制是通过首部的Cache-Control来控制,通过对Cache-Control进行设置,即可实现不同的缓存策略。
Http缓存
Cache-Control和其他的首部字段一样,使用key:value结构,同时value可有多个值, 值之间以,分隔(具体参考 HTTP详解 )。Cache-Control是一个通用首部字段,在Http请求报文中可使用,也可在应答报文中使用。
请求指令集(在请求报文中的取值):
- no-cache: 不要缓存数据,直接从源服务器获取数据;
- no-store: 不缓存请求或响应的任何内容;
- max-age: 表示可接受过期过久的缓存数据,同指定了参数的max-stale;
- max-stale: 表示接收过期的缓存,如后面未指定参数,则表示永远接收缓存数据。如max-stale: 3600, 表示可接受过期1小时内的数据;
- min-fresh: 表示指定时间内的缓存数据仍有效,与缓存是否过期无关。如min-fresh: 60, 表示60s内的缓存数据都有效,60s之后的缓存数据将无效。
- only-if-cache: 表示直接获取缓存数据,若没有数据返回,则返回504(Gateway Timeout)
应答指令集(在应答报文中的取值):
- public: 可向任一方提供缓存数据;
- private: 只向指定用户提供缓存数据;
- no-cache: 缓存前需确认其有效性;
- no-store: 不缓存请求或响应的任何内容;
-
max-age: 表示缓存的最大时间,在此时间范围内,访问该资源时,直接返回缓存数据。不需要对资源的有效性进行确认;
-
must-revalidate: 访问缓存数据时,需要先向源服务器确认缓存数据是否有效,如无法验证其有效性,则需返回504。需要注意的是:如果使用此值,则max-stale将无效。
更详细内容可参考: Http首部字段定义
了解了HTTP的理论知识,后面我们对OkHttp中的缓存进行简单的介绍。
OkHttp拦截器
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缓存
在开始添加缓存策略之前,我们先了解一个完整的缓存策略:
整体来说,在有网络的情况下,使用缓存还是比较复杂,这里我们通过简化版的缓存策略( 有网络时访问服务器,无网络时返回缓存数据 )来演示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(); return response; } }
其次,通过自定义拦截器设置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的缓存
如果觉得OkHttp的缓存太复杂,想自己来缓存数据怎么办呢?有两种方案来实现:
- 自定义拦截器,
- 监听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: 使用这种方案你良心不会痛吗?
这种方案可以说摒弃了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不一致导致访问缓存失败。
@Override // BaseInterceptor.java 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); } @Override // HttpCacheInterceptor.java 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(); }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
无懈可击的Web设计
西德霍姆 / 刘建宁 / 清华大学出版社 / 2009-4 / 59.90元
一个网站,无论视觉上多么美观,内容多么丰富,如果不能面向最广泛的用户群,那它就不算是真正成功的网站。《无懈可击的Web设计:利用XHTML和CSS提高网站的灵活性与适应性》是Web标准设计领域的公认专家Dan Cederholm的倾力之作,向您描述了基于Web标准的设计策略,以适应各种各样的用户浏览方式。书中每一章的开头都给出了一个基于传统HTML技术的实例,然后对它进行重构,指出它的局限性,并利......一起来看看 《无懈可击的Web设计》 这本书的介绍吧!