<轉(zhuǎn)>RxJava+Retrofit+OkHttp深入淺出-終極封裝五(數(shù)據(jù)持久化)

背景

數(shù)據(jù)持久化在現(xiàn)在移動app開發(fā)中已經(jīng)越來越被大家認(rèn)可,提高了用戶體驗和軟件的穩(wěn)定性,但是由于retrofit持久化的局限性,所以需要自己動手改造一個適合自己的數(shù)據(jù)持久化方案!

效果

20161101085509197.gif

第一次請求是網(wǎng)絡(luò)加載,之后只要在設(shè)置的保鮮時間以內(nèi)都是通過緩存拉取數(shù)據(jù),提高加載速度!

下面我們分兩節(jié)講解,一節(jié)講述自帶的retrofit-cache用法和缺陷,一節(jié)講述自己定義的緩存處理方案

Retrofit-cookie

由于retrofit是基于okhttp的,所以他的cache原理就是運用了okhttp的cookie處理;

注意:這里自帶的cookie前提是服務(wù)器提供了支持(返回頭有cache信息),只有g(shù)et請求才具備http的緩存功能,post沒有!沒有!沒有

Retrofit-Cache的內(nèi)容

1.http緩存相關(guān)頭:

Expires (實體標(biāo)頭,HTTP 1.0+):一個GMT時間,試圖告知客戶端,在此日期內(nèi),可以信任并使用對應(yīng)緩存中的副本,缺點是,一但客戶端日期不準(zhǔn)確.則可能導(dǎo)致失效

2.Pragma : no-cache(常規(guī)標(biāo)頭,http1.0+)
3.Cache-Control : (常規(guī)標(biāo)頭,HTTP1.1)
3.1public:(僅為響應(yīng)標(biāo)頭)響應(yīng):告知任何途徑的緩存者,可以無條件的緩存該響應(yīng)
3.2private(僅為響應(yīng)標(biāo)頭):響應(yīng):告知緩存者(據(jù)我所知,是指用戶代理,常見瀏覽器的本地緩存.用戶也是指,系統(tǒng)用戶.但也許,不應(yīng)排除,某些網(wǎng)關(guān),可以識別每個終端用戶的情況),只針對單個用戶緩存響應(yīng). 且可以具體指定某個字段.如private –“username”,則響應(yīng)頭中,名為username的標(biāo)頭內(nèi)容,不會被共享緩存.
3.3no-cache:告知緩存者,必須原原本本的轉(zhuǎn)發(fā)原始請求,并告知任何緩存者,別直接拿你緩存的副本,糊弄人.你需要去轉(zhuǎn)發(fā)我的請求,并驗證你的緩存(如果有的話).對應(yīng)名詞:端對端重載.

cache-retrofit使用

注解使用,具體方法具體設(shè)置(max-age設(shè)置的是保鮮時間)

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();

當(dāng)然我們肯定想要動態(tài)設(shè)置,而且每一個get方法都需要緩存保鮮處理,怎么解決呢?

1.開辟一片本地空間,設(shè)置給OkHttpClient.Builder

OkHttpClient.Builder builder = new OkHttpClient.Builder();
    /*緩存位置和大小*/
  builder.cache(new Cache(MyApplication.app.getCacheDir(),10*1024*1024));

2.設(shè)置攔截器,請求前判斷網(wǎng)絡(luò),攔截數(shù)據(jù)和返回本地數(shù)據(jù)

網(wǎng)上很多資源都是錯誤的,走了很多彎路,注意這里一定要返回一個新的Response 不讓不會有結(jié)果顯示

/**
 * 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)) {//沒網(wǎng)強制從緩存讀取(必須得寫,不然斷網(wǎng)狀態(tài)下,退出應(yīng)用,或者等待一分鐘后,就獲取不到緩存)
            request = request.newBuilder()
                    .cacheControl(CacheControl.FORCE_CACHE)
                    .build();
        }
        Response response = chain.proceed(request);
        Response responseLatest;
        if (isNetworkAvailable(MyApplication.app)) {
            int maxAge = 60; //有網(wǎng)失效一分鐘
            responseLatest = response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .build();
        } else {
            int maxStale = 60 * 60 * 6; // 沒網(wǎng)失效6小時
            responseLatest= response.newBuilder()
                    .removeHeader("Pragma")
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }
        return  responseLatest;
    }
}

有網(wǎng)情況下,一分鐘內(nèi)訪問的請求不會去真正http請求,而是從cache中獲??;
沒網(wǎng)情況下,一律從緩存獲取,6小時過期時間。

3.設(shè)置OkHttpClient.Builder設(shè)置攔截器

addNetworkInterceptor在請求發(fā)生前和發(fā)生后都處理一遍,addInterceptor在有結(jié)果返回后處理一遍
注意:這里一定要兩個方法同時設(shè)置才能保證生效,暫時沒搞懂為什么

     OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.addNetworkInterceptor(new CacheInterceptor());
    builder.addInterceptor(new CacheInterceptor());

現(xiàn)在你的retrofit就能自動給get添加cookie了!

總結(jié)

自帶數(shù)據(jù)持久化處理方便快捷簡單,但是局限性太大,必須是get請求而且還需要服務(wù)器配合頭文件返回處理,所以在實際開發(fā)中并不適用;所以才有了自定義cookie處理的方案

自定義本地數(shù)據(jù)持久化方案

思路

主要是通過greenDao數(shù)據(jù)庫存放數(shù)據(jù),在網(wǎng)絡(luò)請求成功后保存數(shù)據(jù),再次請求判斷url是否已經(jīng)存在緩存數(shù)據(jù)
有網(wǎng)絡(luò):onstart中判斷再判斷保鮮時間,如果有效返回緩存數(shù)據(jù),無效則再一次請求數(shù)據(jù)!
無網(wǎng)絡(luò)(包含各種失敗):onError中判斷處理,有效時間內(nèi)返回數(shù)據(jù),無效自定義的網(wǎng)絡(luò)錯誤拋出異常!

1.創(chuàng)建緩存對象數(shù)據(jù)

記錄返回數(shù)據(jù),標(biāo)識url,和緩存時間
/**

  • post請求緩存數(shù)據(jù)
  • Created by WZG on 2016/10/26.
    /
    @Entity
    public class CookieResulte {
    @Id
    private long id;
    /
    url/
    private String url;
    /
    返回結(jié)果/
    private String resulte;
    /
    時間*/
    private long time;
    }
2.BaseApi添加緩存相關(guān)設(shè)置參數(shù)

保持和封裝1-4封裝的一致性,將緩存的相關(guān)設(shè)置放入在BaseApi中,并且將baseUrl和超時connectionTime也包含進(jìn)來,更加靈活

/**
 * 請求數(shù)據(jù)統(tǒng)一封裝類
 * Created by WZG on 2016/7/16.
 */
public abstract class BaseApi<T> implements Func1<BaseResultEntity<T>, T> {
    //rx生命周期管理
    private SoftReference<RxAppCompatActivity> rxAppCompatActivity;
    /*回調(diào)*/
    private SoftReference<HttpOnNextListener> listener;
    /*是否能取消加載框*/
    private boolean cancel;
    /*是否顯示加載框*/
    private boolean showProgress;
    /*是否需要緩存處理*/
    private boolean cache;
    /*基礎(chǔ)url*/
    private  String baseUrl="http://www.izaodao.com/Api/";
    /*方法-如果需要緩存必須設(shè)置這個參數(shù);不需要不用設(shè)置*/
    private String mothed;
    /*超時時間-默認(rèn)6秒*/
    private int connectionTime = 6;
    /*有網(wǎng)情況下的本地緩存時間默認(rèn)60秒*/
    private int cookieNetWorkTime=60;
    /*無網(wǎng)絡(luò)的情況下本地緩存時間默認(rèn)30天*/
    private int cookieNoNetWorkTime=24*60*60*30;
}

注意:如果需要使用緩存功能必須要設(shè)置mothed參數(shù)(和baseurl拼成一個url標(biāo)識緩存數(shù)據(jù))

3.攔截Gson數(shù)據(jù)

由于使用GsonConverterFactory自動解析數(shù)據(jù),所以需要在自動轉(zhuǎn)換前得到服務(wù)器返回的數(shù)據(jù),我們可以自定義Interceptor在addInterceptor(成功后調(diào)用)攔截數(shù)據(jù),保存到本地數(shù)據(jù)庫中!

/**
 * gson持久化截取保存數(shù)據(jù)
 * Created by WZG on 2016/10/20.
 */
public class CookieInterceptor implements Interceptor {
    private CookieDbUtil dbUtil;
    /*是否緩存標(biāo)識*/
    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();
            /*保存和更新本地數(shù)據(jù)*/
            if(resulte==null){
                resulte  =new CookieResulte(url,bodyString,time);
                dbUtil.saveCookie(resulte);
            }else{
                resulte.setResulte(bodyString);
                resulte.setTime(time);
                dbUtil.updateCookie(resulte);
            }
        }
        return response;
    }
}
4.添加回調(diào)方法

因為緩存回調(diào)過程中無法手動傳遞Gson對象,也就是ResulteEntity中的T泛型,所以自由單獨添加一個方法,返回緩存數(shù)據(jù)!考慮到可能不需要回到所以寫成了具體的方法,可主動覆蓋!

/**
 * 成功回調(diào)處理
 * Created by WZG on 2016/7/16.
 */
public abstract class HttpOnNextListener<T> {
    /**
     * 成功后回調(diào)方法
     * @param t
     */
    public abstract void onNext(T t);

    /**
     * 緩存回調(diào)結(jié)果
     * @param string
     */
    public void onCacheNext(String string){

    }
    *********
}
5.數(shù)據(jù)持久化調(diào)用,獲取緩存

這里分兩種情況,有網(wǎng)絡(luò)-和無網(wǎng)絡(luò)(包含各種失敗不單單只是無網(wǎng)絡(luò))

有網(wǎng)

判斷是否存在緩存,如果有判斷保鮮時間,有效期內(nèi)返回數(shù)據(jù),失效在一起請求;

 /**
     * 訂閱開始時調(diào)用
     * 顯示ProgressDialog
     */
    @Override
    public void onStart() {
        showProgressDialog();
        /*緩存并且有網(wǎng)*/
        if(api.isCache()&& AppUtil.isNetworkAvailable(MyApplication.app)){
             /*獲取緩存數(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();
                }
            }
        }
    }
無網(wǎng)絡(luò)(失敗情況)

原理和有網(wǎng)絡(luò)一樣,但是額外的加入了rx異常處理,防止用戶在處理工程中導(dǎo)致錯誤崩潰!并且無緩沖拋出自定義異常

    /**
     * 對錯誤進(jìn)行統(tǒng)一處理
     * 隱藏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) {
                    /*獲取緩存數(shù)據(jù)*/
                    CookieResulte cookieResulte= CookieDbUtil.getInstance().queryCookieBy(s);
                    if(cookieResulte==null){
                        throw new HttpTimeException("網(wǎng)絡(luò)錯誤");
                    }
                    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("網(wǎng)絡(luò)錯誤");
                    }
                }
            });
        }else{
            errorDo(e);
        }
    }
6.回調(diào)解析數(shù)據(jù)

由于是返回的string數(shù)據(jù),所以需要在回調(diào)onCacheNext中手動解析Gson數(shù)據(jù)


    //   回調(diào)一一對應(yīng)
    HttpOnNextListener simpleOnNextListener = new HttpOnNextListener<List<SubjectResulte>>() {
        @Override
        public void onNext(List<SubjectResulte> subjects) {
            tvMsg.setText("網(wǎng)絡(luò)返回:\n" + subjects.toString());
        }

        @Override
        public void onCacheNext(String cache) {
            /*緩存回調(diào)*/
            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() );
        }
    };

好了,一套自定義的緩存方案就解決了!

總結(jié)

優(yōu)點:

1.有效的解決了post請求緩存的問題
2.可以同時緩存get數(shù)據(jù)
3.自定義更加靈活,可更換任意第三方庫

缺點:

1.緩存數(shù)據(jù)無法和onext公用一個回到接口,導(dǎo)致需要手動解析數(shù)據(jù)(由于Gson自動轉(zhuǎn)換導(dǎo)致)

由于Gson在回調(diào)的過程中和使用過程中給程序?qū)е碌囊恍┝械南拗疲詻Q定封裝一個變種框架,去掉Gson自動解析回調(diào)功能,改用String回調(diào),讓回調(diào)接口一對多處理,并且解決緩存無法和成功統(tǒng)一回調(diào)的問題!歡迎大家關(guān)注!

如有幫助換start和follow!

終極封裝專欄

RxJava+Retrofit+OkHttp深入淺出-終極封裝專欄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容