简介
OkHttp是由Square公司开发的开源网络访问库,目前在Android和Java开发中有着广泛的应用。在Android开发中和Retrofit结合可以非常方便地调用网络接口。
缓存
使用缓存可以让我们的app不用长时间地显示令人厌烦的加载圈,提高了用户体验,而且还节省了流量,在数据更新不是很频繁的地方使用缓存就非常有必要了。想要加入缓存不需要我们自己来实现,Okhttp已经内置了缓存,默认是不使用的,如果想使用缓存我们需要手动设置。
缓存策略
- 基于OkHttp内置缓存
- 服务器支持
- 服务器不支持
- 通过拦截器实现缓存
基于OkHttp内置缓存
服务器支持缓存
如果服务器支持缓存,请求返回的Response会带有这样的Header:Cache-Control, max-age=xxx
,这种情况下我们只需要手动给okhttp设置缓存就可以让OkHttp自动帮你缓存了。这里的max-age
的值代表了缓存在你本地存放的时间,可以根据实际需要来设置其大小。
首先我们要提供了一个文件路径用来存放缓存,出于安全性的考虑,在Android中我们推荐使用Context.getCacheDir()
来作为缓存的存放路径,另外还需要指定缓存的大小就可以创建一个缓存了。show code:
// 指定缓存路径,缓存大小100Mb
Cache cache = new Cache(new File(mContext.getContext().getCacheDir(), "HttpCache"),
1024 * 1024 * 100);
初始化缓存对象之后把它设置到OkHttpClient
对象里面去
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.build();
服务器不支持缓存
如果服务器不支持缓存就可能没有指定这个头部,或者指定的值是如no-store
等,但是我们还想在本地使用缓存的话要怎么办呢?这种情况下我们就需要使用Interceptor
来重写Respose
的头部信息,从而让OkHttp
支持缓存。
如下所示,我们重写的Response
的Cache-Control
字段
private static final Interceptor sCacheInterceptor = new Interceptor (){
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response.newBuilder()
//// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
.removeHeader("Pragma")
.removeHeader("Cache-Control")
//cache for 30 days
.header("Cache-Control", "max-age=" + 3600 * 24 * 30)
.build();
}
}
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(sCacheInterceptor)
.build();
另外
在无网络下获取缓存
/**
* 云端响应头拦截器,用来配置缓存策略
*/
private static final Interceptor sRewriteCacheControlInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetUtil.isNetworkAvailable(AndroidApplication.getContext())) {
request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
}
Response originalResponse = chain.proceed(request);
if (NetUtil.isNetworkAvailable(AndroidApplication.getContext())) {
//有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
String cacheControl = request.cacheControl().toString();
return originalResponse.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
return originalResponse.newBuilder()
.header("Cache-Control", "public, " + CACHE_CONTROL_CACHE)
.removeHeader("Pragma")
.build();
}
}
};
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(sCacheInterceptor)
.addInterceptor(sRewriteCacheControlInterceptor)
.build();
在无网络的话进行如上配置会直接走Subscriber
的OnNext()
而不是OnError()
通过拦截器实现缓存
public class CookieInterceptor implements Interceptor{
//缓存的工具类 这里我是用GreenDao封装的一个工具类
private CookieDbUtil dbUtil;
/**
* 是否缓存标识
*/
private boolean cache;
private String url;
public CookieInterceptor(boolean cache, String url) {
dbUtil = CookieDbUtil.getInstance();
this.cache = cache;
this.url = url;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (cache){
ResponseBody body = response.body();
BufferedSource source = body.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = Charset.defaultCharset();
MediaType contentType = body.contentType();
if (contentType != null){
charset = contentType.charset(charset);
}
String bodyString = buffer.clone().readString(charset);
CookieResult result = dbUtil.queryCookieBy(url);
long time = System.currentTimeMillis();
/**
* 保存和更新到本地数据
*/
if (result == null){
result = new CookieResult(url,bodyString,time);
dbUtil.saveCookie(result);
}else {
result.setResult(bodyString);
result.setTime(time);
dbUtil.updateCookie(result);
}
}
return response;
}
}
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CookieInterceptor(isCache(),getUrl()) )
.build();
通过自定义拦截器就实现了缓存到本地的策略,那怎么在不影响当前逻辑下读取缓存呢?对的,你说的对!通过重写Subscriber
,在onStart()
和onError()
方法进行处理,主要代码如下:
/**
*
* 开始之前优先尝试读取本地缓存
*/
@Override
public void onStart() {
/*缓存并且有网*/
if (api.isCache() && AppUtil.isNetworkAvailable(RxRetrofitApp.getApplication())) {
/*获取缓存数据*/
CookieResulte cookieResulte = CookieDbUtil.getInstance().queryCookieBy(api.getUrl());
if (cookieResulte != null) {
long time = (System.currentTimeMillis() - cookieResulte.getTime()) / 1000;
if (time < api.getCookieNetWorkTime()) {
if (mSubscriberOnNextListener.get() != null) {
mSubscriberOnNextListener.get().onCacheNext(cookieResulte.getResulte());
}
onCompleted();
unsubscribe();
}
}
}
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
/*需要緩存并且本地有缓存才返回*/
if (isCache()) {
Observable.just(api.getUrl()).subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
errorDo(e);//错误的统一处理
}
@Override
public void onNext(String s) {
/*获取缓存数据*/
CookieResulte cookieResulte = CookieDbUtil.getInstance().queryCookieBy(s);
if (cookieResulte == null) {
throw new HttpTimeException("网络错误");
}
long time = (System.currentTimeMillis() - cookieResulte.getTime()) / 1000;
if (time < api.getCookieNoNetWorkTime()) {
if (mSubscriberOnNextListener.get() != null) {
mSubscriberOnNextListener.get().onCacheNext(cookieResulte.getResulte());
}
} else {
CookieDbUtil.getInstance().deleteCookie(cookieResulte);
throw new HttpTimeException("网络错误");
}
}
});
} else {
errorDo(e);
}
}
仔细的童鞋可能发现mSubscriberOnNextListener
对象不知道从哪里来的,这里定义了一个接口
public abstract class HttpOnNextListener<T> {
/**
* 成功后回调方法
* @param t
*/
public abstract void onNext(T t);
/**
* 緩存回調結果
* @param string
*/
public void onCacheNext(String string){
}
/**
* 失败或者错误方法
* 主动调用,更加灵活
* @param e
*/
public void onError(Throwable e){
}
/**
* 取消回調
*/
public void onCancel(){
}
}
调用时可以传入接口进行回调处理
比较、反思
也许你会觉得第二种方式比第一种麻烦多了,你这么写出来肯定有你的用意吧
很遗憾,好像没啥优势!!!
以此记录我一开始不知道第一种方式而入的坑