簡介
OkHttp是由Square公司開發(fā)的開源網絡訪問庫自脯,目前在Android和Java開發(fā)中有著廣泛的應用楼眷。在Android開發(fā)中和Retrofit結合可以非常方便地調用網絡接口铲汪。
緩存
使用緩存可以讓我們的app不用長時間地顯示令人厭煩的加載圈熊尉,提高了用戶體驗,而且還節(jié)省了流量掌腰,在數(shù)據(jù)更新不是很頻繁的地方使用緩存就非常有必要了帽揪。想要加入緩存不需要我們自己來實現(xiàn),Okhttp已經內置了緩存辅斟,默認是不使用的转晰,如果想使用緩存我們需要手動設置。
緩存策略
- 基于OkHttp內置緩存
- 服務器支持
- 服務器不支持
- 通過攔截器實現(xiàn)緩存
基于OkHttp內置緩存
服務器支持緩存
如果服務器支持緩存士飒,請求返回的Response會帶有這樣的Header:Cache-Control, max-age=xxx
,這種情況下我們只需要手動給okhttp設置緩存就可以讓OkHttp自動幫你緩存了查邢。這里的max-age
的值代表了緩存在你本地存放的時間,可以根據(jù)實際需要來設置其大小酵幕。
首先我們要提供了一個文件路徑用來存放緩存扰藕,出于安全性的考慮,在Android中我們推薦使用Context.getCacheDir()
來作為緩存的存放路徑芳撒,另外還需要指定緩存的大小就可以創(chuàng)建一個緩存了邓深。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里的配置爱咬,你可以在這里進行統(tǒng)一的設置
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()
通過攔截器實現(xiàn)緩存
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();
/**
* 保存和更新到本地數(shù)據(jù)
*/
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();
通過自定義攔截器就實現(xiàn)了緩存到本地的策略尺借,那怎么在不影響當前邏輯下讀取緩存呢?對的精拟,你說的對!通過重寫Subscriber
燎斩,在onStart()
和onError()
方法進行處理,主要代碼如下:
/**
*
* 開始之前優(yōu)先嘗試讀取本地緩存
*/
@Override
public void onStart() {
/*緩存并且有網*/
if (api.isCache() && AppUtil.isNetworkAvailable(RxRetrofitApp.getApplication())) {
/*獲取緩存數(shù)據(jù)*/
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);//錯誤的統(tǒng)一處理
}
@Override
public void onNext(String s) {
/*獲取緩存數(shù)據(jù)*/
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);
}
}
仔細的童鞋可能發(fā)現(xiàn)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(){
}
}
調用時可以傳入接口進行回調處理
比較瘫里、反思
也許你會覺得第二種方式比第一種麻煩多了,你這么寫出來肯定有你的用意吧
很遺憾荡碾,好像沒啥優(yōu)勢=鞫痢!坛吁!
以此記錄我一開始不知道第一種方式而入的坑