基于OkHttp緩存策略

簡介

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支持緩存舌菜。
如下所示萌壳,我們重寫的ResponseCache-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();

在無網絡的話進行如上配置會直接走SubscriberOnNext()而不是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)勢=鞫痢!坛吁!

以此記錄我一開始不知道第一種方式而入的坑

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末劳殖,一起剝皮案震驚了整個濱河市铐尚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哆姻,老刑警劉巖宣增,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異矛缨,居然都是意外死亡爹脾,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門箕昭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灵妨,“玉大人,你說我怎么就攤上這事落竹∶诨簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵述召,是天一觀的道長朱转。 經常有香客問我,道長积暖,這世上最難降的妖魔是什么藤为? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮呀酸,結果婚禮上凉蜂,老公的妹妹穿的比我還像新娘琼梆。我一直安慰自己性誉,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布茎杂。 她就那樣靜靜地躺著错览,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煌往。 梳的紋絲不亂的頭發(fā)上倾哺,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音刽脖,去河邊找鬼羞海。 笑死,一個胖子當著我的面吹牛曲管,可吹牛的內容都是我干的却邓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼院水,長吁一口氣:“原來是場噩夢啊……” “哼腊徙!你這毒婦竟也來了简十?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撬腾,失蹤者是張志新(化名)和其女友劉穎螟蝙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民傻,經...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胰默,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了漓踢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初坠。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彭雾,靈堂內的尸體忽然破棺而出碟刺,到底是詐尸還是另有隱情,我是刑警寧澤薯酝,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布半沽,位于F島的核電站,受9級特大地震影響吴菠,放射性物質發(fā)生泄漏者填。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一做葵、第九天 我趴在偏房一處隱蔽的房頂上張望占哟。 院中可真熱鬧,春花似錦酿矢、人聲如沸榨乎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜜暑。三九已至,卻和暖如春策肝,著一層夾襖步出監(jiān)牢的瞬間肛捍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工之众, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拙毫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓棺禾,卻偏偏與公主長得像缀蹄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,139評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理袍患,服務發(fā)現(xiàn)坦康,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • Okhttp使用指南與源碼分析 標簽(空格分隔): Android 使用指南篇# 為什么使用okhttp### A...
    背影殺手不太冷閱讀 8,152評論 2 119
  • NSString *str = @" this is a test . "; 去掉兩端的空格 str = [str...
    王_堯閱讀 5,804評論 0 5
  • 愛情的語言很神奇诡延,簡單的字組合起來就觸動心靈滞欠,喚醒塵封在遙遠邊界的心。 一個聲音一次又一次穿越神經...
    希波克林閱讀 342評論 5 6