數據持久化在如今移動app開發中已經愈來愈被你們承認,提升了用戶體驗和軟件的穩定性,可是因爲retrofit持久化的侷限性,因此須要本身動手改造一個適合本身的數據持久化方案!html
下面咱們分兩節講解,一節講述自帶的retrofit-cache用法和缺陷,一節講述本身定義的緩存處理方案java
因爲retrofit是基於okhttp的,因此他的cache原理就是運用了okhttp的cookie處理;git
注意:這裏自帶的cookie前提是服務器提供了支持(返回頭有cache信息),只有get請求才具有http的緩存功能,post沒有!沒有!沒有github
1.http緩存相關頭:Expires (實體標頭,HTTP1.0+):一個GMT時間,試圖告知客戶端,在此日期內,能夠信任並使用對應緩存中的副本,缺點是,一但客戶端日期不許確.則可能致使失效數據庫
2.Pragma : no-cache(常規標頭,http1.0+) api
3.Cache-Control : (常規標頭,HTTP1.1)瀏覽器
3.1 public:(僅爲響應標頭)響應:告知任何途徑的緩存者,能夠無條件的緩存該響應緩存
3.2 private(僅爲響應標頭):響應:告知緩存者(據我所知,是指用戶代理,常見瀏覽器的本地緩存.用戶也是指,系統用戶.但也許,不該排除,某些網關,能夠識別每一個終端用戶的狀況),只針對單個用戶緩存響應. 且能夠具體指定某個字段.如private –「username」,則響應頭中,名爲username的標頭內容,不會被共享緩存.服務器
3.3 no-cache:告知緩存者,必須原本來本的轉發原始請求,並告知任何緩存者,別直接拿你緩存的副本,糊弄人.你須要去轉發個人請求,並驗證你的緩存(若是有的話).對應名詞:端對端重載.cookie
註解使用,具體方法具體設置(max-age設置的是保鮮時間)
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();複製代碼
固然咱們確定想要動態設置,並且每個get方法都須要緩存保鮮處理,怎麼解決呢?
OkHttpClient.Builder builder = new OkHttpClient.Builder();
/*緩存位置和大小*/
builder.cache(new Cache(MyApplication.app.getCacheDir(),10*1024*1024));複製代碼
網上不少資源都是錯誤的,走了不少彎路,注意這裏必定要返回一個新的Response 不讓不會有結果顯示
/** * get緩存方式攔截器 * Created by WZG on 2016/10/26. */
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!isNetworkAvailable(MyApplication.app)) {//沒網強制從緩存讀取(必須得寫,否則斷網狀態下,退出應用,或者等待一分鐘後,就獲取不到緩存)
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
Response responseLatest;
if (isNetworkAvailable(MyApplication.app)) {
int maxAge = 60; //有網失效一分鐘
responseLatest = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 6; // 沒網失效6小時
responseLatest= response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
return responseLatest;
}
}複製代碼
有網狀況下,一分鐘內訪問的請求不會去真正http請求,而是從cache中獲取;
沒網狀況下,一概從緩存獲取,6小時過時時間。
addNetworkInterceptor在請求發生前和發生後都處理一遍,addInterceptor在有結果返回後處理一遍
注意:這裏必定要兩個方法同時設置才能保證生效,暫時沒搞懂爲何
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addNetworkInterceptor(new CacheInterceptor());
builder.addInterceptor(new CacheInterceptor());複製代碼
如今你的retrofit就能自動給get添加cookie了!
自帶數據持久化處理方便快捷簡單,可是侷限性太大,必須是get請求並且還須要服務器配合頭文件返回處理,因此在實際開發中並不適用;因此纔有了自定義cookie處理的方案
主要是經過greenDao數據庫存放數據,在網絡請求成功後保存數據,再次請求判斷url是否已經存在緩存數據
有網絡:onstart中判斷再判斷保鮮時間,若是有效返回緩存數據,無效則再一次請求數據!
無網絡(包含各類失敗):onError中判斷處理,有效時間內返回數據,無效自定義的網絡錯誤拋出異常!
記錄返回數據,標識url,和緩存時間
/** * post請求緩存數據 * Created by WZG on 2016/10/26. */
@Entity
public class CookieResulte {
@Id
private long id;
/*url*/
private String url;
/*返回結果*/
private String resulte;
/*時間*/
private long time;
}複製代碼
保持和封裝1-4封裝的一致性,將緩存的相關設置放入在BaseApi中,而且將baseUrl和超時connectionTime也包含進來,更加靈活
/** * 請求數據統一封裝類 * Created by WZG on 2016/7/16. */
public abstract class BaseApi<T> implements Func1<BaseResultEntity<T>, T> {
//rx生命週期管理
private SoftReference<RxAppCompatActivity> rxAppCompatActivity;
/*回調*/
private SoftReference<HttpOnNextListener> listener;
/*是否能取消加載框*/
private boolean cancel;
/*是否顯示加載框*/
private boolean showProgress;
/*是否須要緩存處理*/
private boolean cache;
/*基礎url*/
private String baseUrl="http://www.izaodao.com/Api/";
/*方法-若是須要緩存必須設置這個參數;不須要不用設置*/
private String mothed;
/*超時時間-默認6秒*/
private int connectionTime = 6;
/*有網狀況下的本地緩存時間默認60秒*/
private int cookieNetWorkTime=60;
/*無網絡的狀況下本地緩存時間默認30天*/
private int cookieNoNetWorkTime=24*60*60*30;
}複製代碼
注意:若是須要使用緩存功能必需要設置mothed參數(和baseurl拼成一個url標識緩存數據)
因爲使用GsonConverterFactory自動解析數據,因此須要在自動轉換前獲得服務器返回的數據,咱們能夠自定義Interceptor在addInterceptor(成功後調用)攔截數據,保存到本地數據庫中!
/** * gson持久化截取保存數據 * Created by WZG on 2016/10/20. */
public class CookieInterceptor implements Interceptor {
private CookieDbUtil dbUtil;
/*是否緩存標識*/
private boolean cache;
public CookieInterceptor( boolean cache) {
dbUtil=CookieDbUtil.getInstance();
this.cache=cache;
}
@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 the entire body.
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);
String url = request.url().toString();
CookieResulte resulte= dbUtil.queryCookieBy(url);
long time=System.currentTimeMillis();
/*保存和更新本地數據*/
if(resulte==null){
resulte =new CookieResulte(url,bodyString,time);
dbUtil.saveCookie(resulte);
}else{
resulte.setResulte(bodyString);
resulte.setTime(time);
dbUtil.updateCookie(resulte);
}
}
return response;
}
}複製代碼
由於緩存回調過程當中沒法手動傳遞Gson對象,也就是ResulteEntity中的T泛型,因此自由單獨添加一個方法,返回緩存數據!考慮到可能不須要回到因此寫成了具體的方法,可主動覆蓋!
/** * 成功回調處理 * Created by WZG on 2016/7/16. */
public abstract class HttpOnNextListener<T> {
/** * 成功後回調方法 * @param t */
public abstract void onNext(T t);
/** * 緩存回調結果 * @param string */
public void onCacheNext(String string){
}
*********
}複製代碼
這裏分兩種狀況,有網絡-和無網絡(包含各類失敗不僅僅只是無網絡)
有網
判斷是否存在緩存,若是有判斷保鮮時間,有效期內返回數據,失效在一塊兒請求;
/** * 訂閱開始時調用 * 顯示ProgressDialog */
@Override
public void onStart() {
showProgressDialog();
/*緩存而且有網*/
if(api.isCache()&& AppUtil.isNetworkAvailable(MyApplication.app)){
/*獲取緩存數據*/
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();
}
}
}
}複製代碼
無網絡(失敗狀況)
原理和有網絡同樣,可是額外的加入了rx異常處理,防止用戶在處理工程中致使錯誤崩潰!而且無緩衝拋出自定義異常
/** * 對錯誤進行統一處理 * 隱藏ProgressDialog * * @param e */
@Override
public void onError(Throwable e) {
dismissProgressDialog();
/*須要緩存而且本地有緩存才返回*/
if(api.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);
}
}複製代碼
因爲是返回的string數據,因此須要在回調onCacheNext中手動解析Gson數據
// 回調一一對應
HttpOnNextListener simpleOnNextListener = new HttpOnNextListener<List<SubjectResulte>>() {
@Override
public void onNext(List<SubjectResulte> subjects) {
tvMsg.setText("網絡返回:\n" + subjects.toString());
}
@Override
public void onCacheNext(String cache) {
/*緩存回調*/
Gson gson=new Gson();
java.lang.reflect.Type type = new TypeToken<BaseResultEntity<List<SubjectResulte>>>() {}.getType();
BaseResultEntity resultEntity= gson.fromJson(cache, type);
tvMsg.setText("緩存返回:\n"+resultEntity.getData().toString() );
}
};複製代碼
好了,一套自定義的緩存方案就解決了!
優勢:
1.有效的解決了post請求緩存的問題
2.能夠同時緩存get數據
3.自定義更加靈活,可更換任意第三方庫
缺點:
1.緩存數據沒法和onext公用一個回到接口,致使須要手動解析數據(因爲Gson自動轉換致使)
因爲Gson在回調的過程當中和使用過程當中給程序致使的一些列的限制,因此決定封裝一個變種框架,去掉Gson自動解析回調功能,改用String回調,讓回調接口一對多處理,而且解決緩存沒法和成功統一回調的問題!歡迎你們關注!
若有幫助換start和follow!