Android-Retrofit-超時(shí)-重試-緩存-攔截器

0. Thanks To

Retrofit使用詳解(一)
Android Retrofit 2.0 的詳細(xì) 使用攻略(含實(shí)例講解)
Android Retrofit網(wǎng)絡(luò)請求Service另萤,@Path性昭、@Query简十、@QueryMap、@Map...
急速開發(fā)系列——Retrofit實(shí)戰(zhàn)技巧
retrofit2.0緩存設(shè)置
OkHttp自定義重試次數(shù)
okhttp 日志攔截器Logging-interceptor

1.超時(shí)

  • 通過 OkHttpClient.Builder 去設(shè)置仔燕。
OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        .build();

傳入builder設(shè)置:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("xxx") //設(shè)置網(wǎng)絡(luò)請求的Url地址
        .addConverterFactory(GsonConverterFactory.create()) //設(shè)置數(shù)據(jù)解析器
        .client(client)
        .build();

2.重試

  • 通過Client設(shè)置重試舟误,重試一次葡秒。
OkHttpClient client = new OkHttpClient.Builder()
        .retryOnConnectionFailure(true)//默認(rèn)重試一次,若需要重試N次,則要實(shí)現(xiàn)攔截器眯牧。
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        .build();
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("xxx") //設(shè)置網(wǎng)絡(luò)請求的Url地址
        .addConverterFactory(GsonConverterFactory.create()) //設(shè)置數(shù)據(jù)解析器
        .client(client)
        .build();
  • 以上的重試只能重試一次蹋岩,若需要重試N次,可以通過設(shè)置攔截器
/**
 * 自定義的学少,重試N次的攔截器
 * 通過:addInterceptor 設(shè)置
 */
public static class Retry implements Interceptor {
    public int maxRetry;//最大重試次數(shù)
    private int retryNum = 0;//假如設(shè)置為3次重試的話剪个,則最大可能請求4次(默認(rèn)1次+3次重試)
    public Retry(int maxRetry) {
        this.maxRetry = maxRetry;
    }
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        Log.i("Retry","num:"+retryNum);
        while (!response.isSuccessful() && retryNum < maxRetry) {
            retryNum++;
            Log.i("Retry","num:"+retryNum);
            response = chain.proceed(request);
        }
        return response;
    }
}

當(dāng)在有網(wǎng)絡(luò)的情況下,網(wǎng)絡(luò)是暢通的版确,但獲取失敗后扣囊,那么會(huì)跑以上的攔截了,重新嘗試N次绒疗。

3.緩存

設(shè)置緩存的的兩種方式

  • 1) 通過添加 @Headers("Cache-Control: max-age=120") 進(jìn)行設(shè)置侵歇。添加了Cache-Control 的請求,retrofit 會(huì)默認(rèn)緩存該請求的返回?cái)?shù)據(jù)一般來說吓蘑,這種方法是針對特定的API進(jìn)行設(shè)置惕虑。
@Headers("Cache-Control:public,max-age=120")
@GET("mobile/active")
Call<ResponseBody> getActive(@Query("id") int activeId);

這樣我們就通過@Headers快速的為該api添加了緩存控制。120s內(nèi)士修,緩存都是生效狀態(tài)枷遂,即無論有網(wǎng)無網(wǎng)都讀取緩存。

  • 2)通過Interceptors實(shí)現(xiàn)緩存棋嘲。

  • 這兩者實(shí)現(xiàn)原理一致酒唉,但是適用場景不同。通常是使用Interceptors來設(shè)置通用緩存策略沸移,而通過@Header針對某個(gè)請求單獨(dú)設(shè)置緩存策略痪伦。另外,一定要記住雹锣,retrofit 2.0底層依賴OkHttp實(shí)現(xiàn)网沾,這也就意味著retrofit緩存的實(shí)現(xiàn)同樣是借助OkHttp來的。另外蕊爵,無論你是決定使用那種形勢的緩存辉哥,首先要為OkHttpClient設(shè)置Cache,否則緩存不會(huì)生效(retrofit并未置默認(rèn)緩存目錄)攒射。

public static Interceptor getCacheInterceptor() {
       return new Interceptor() {
           @Override
           public Response intercept(Chain chain) throws IOException {
               Request request = chain.request();
               Response response = chain.proceed(request);
               return response.newBuilder().header("Cache-Control","public,max-age=120").build();
           }

       };
   }
  • addNetworkInterceptoraddInterceptor
    兩個(gè)方法同樣是添加攔截器醋旦,addNetworkInterceptor添加的是網(wǎng)絡(luò)攔截器,在網(wǎng)絡(luò)暢通的時(shí)候會(huì)調(diào)用会放,而addInterceptor則都會(huì)調(diào)用饲齐。所以我們應(yīng)該是在addInterceptor去寫邏輯。

  • 代碼如下:

//聲明緩存地址和大小
Cache cache = new Cache(this.getCacheDir(),10*1024*1024);
//構(gòu)建 Client
OkHttpClient client = new OkHttpClient.Builder()
        .retryOnConnectionFailure(true)
        .addNetworkInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response response = chain.proceed(request);
                return response.newBuilder().header("Cache-Control","public,max-age=20").build();
            }
        })
        .cache(cache)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        .build();
//構(gòu)建 Retrofit
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("xxx") //設(shè)置網(wǎng)絡(luò)請求的Url地址
        .addConverterFactory(GsonConverterFactory.create()) //設(shè)置數(shù)據(jù)解析器
        .client(client)
        .build();
// 創(chuàng)建 網(wǎng)絡(luò)請求接口 的實(shí)例
GetAppList request = retrofit.create(GetAppList.class);
Call<AppListBean> call = request.get(1,8);
call.enqueue(new Callback<AppListBean>() {
    @Override
    public void onResponse(Call<AppListBean> call, retrofit2.Response<AppListBean> response
        if (response!=null && response.isSuccessful()) {
            if (response.body()!=null && response.body().data!=null)
                for (AppListBean.DataBean d :
                        response.body().data) {
                    LogUtils.i(TAG,d.toString());
                }
        }
    }
    @Override
    public void onFailure(Call<AppListBean> call, Throwable t) {
        LogUtils.i(TAG,t.getMessage());
    }
});
  • 而咧最,現(xiàn)在我們需要這樣的一個(gè)策略:在無網(wǎng)絡(luò)的情況下讀取緩存捂人,而且網(wǎng)絡(luò)下的緩存也有過期時(shí)間御雕,有網(wǎng)絡(luò)的情況下根據(jù)緩存的過期時(shí)間重新請求,修改攔截器的邏輯:
//參考:http://blog.csdn.net/changsimeng/article/details/54668884
OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (!NetworkUtils.isConnected(MainActivity.this)) {
                    int maxStale = 4 * 7 * 24 * 60; // 離線時(shí)緩存保存4周,單位:秒
                    CacheControl tempCacheControl = new CacheControl.Builder()
                            .onlyIfCached()
                            .maxStale(maxStale, TimeUnit.SECONDS)
                            .build();
                    request = request.newBuilder()
                            .cacheControl(tempCacheControl)
                            .build();
                }
                return chain.proceed(request);
            }
        })
        .addNetworkInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Response originalResponse = chain.proceed(request);
                int maxAge = 20;    // 在線緩存,單位:秒
                return originalResponse.newBuilder()
                        .removeHeader("Pragma")// 清除頭信息滥搭,因?yàn)榉?wù)器如果不支持酸纲,會(huì)返回一些干擾信息,不清除下面無法生效
                        .removeHeader("Cache-Control")
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .build();
            }
        })
        .cache(cache)
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(20, TimeUnit.SECONDS)
        .writeTimeout(20, TimeUnit.SECONDS)
        .build();

那么瑟匆,有網(wǎng)絡(luò)的情況下福青,緩存時(shí)間是:20秒。也就是在20秒內(nèi)的請求都是獲取本地的緩存脓诡。當(dāng)網(wǎng)絡(luò)斷開后无午,會(huì)設(shè)置一個(gè)離線的緩存,為4周祝谚。

  • 3)關(guān)于max-age和max-stale

  • maxAge :設(shè)置最大失效時(shí)間宪迟,失效則不使用

  • maxStale :設(shè)置最大失效時(shí)間,失效則不使用

  • max-stale在請求頭設(shè)置有效交惯,在響應(yīng)頭設(shè)置無效次泽。

  • max-stale和max-age同時(shí)設(shè)置的時(shí)候,緩存失效的時(shí)間按最長的算席爽。

4.攔截器

  • 在上面大家已經(jīng)看到過攔截器的用法了意荤,自定義的攔截器需要復(fù)寫以下的接口:
public Response intercept(Interceptor.Chain chain) throws IOException

其中的chain就是包含了,request和respone只锻,所以你想要什么都可以從這里獲取到玖像。

Request request = chain.request();
Response response = chain.proceed(request);
  • 這里示例寫一個(gè),實(shí)現(xiàn)打印回包的日志攔截器:
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
    Request request = chain.request();
    long t1 = System.currentTimeMillis();//請求發(fā)起的時(shí)間
    Response response = chain.proceed(request);
    long t2 = System.currentTimeMillis();//收到響應(yīng)的時(shí)間
    //這里不能直接使用response.body().string()的方式輸出日志
    //因?yàn)閞esponse.body().string()之后齐饮,response中的流會(huì)被關(guān)閉捐寥,程序會(huì)報(bào)錯(cuò),我們需要?jiǎng)?chuàng)建出一
    //個(gè)新的response給應(yīng)用層處理
    ResponseBody responseBody = response.peekBody(1024 * 1024);
    Log.i("CommonLog",response.request().url()+ " , use-timeMs: " + (t2 - t1) + " , data: "+responseBody.string());
    return response;
}

5.最后

  • 在上面的代碼中祖驱,我們有用到一個(gè)判斷當(dāng)前是否連著網(wǎng)絡(luò)的工具類:
 /**
  * 判斷網(wǎng)絡(luò)是否連接
  * <p>需添加權(quán)限 {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>}</p>
  *
  * @param context 上下文
  * @return {@code true}: 是<br>{@code false}: 否
  */
 public static boolean isConnected(Context context) {
     NetworkInfo info = getActiveNetworkInfo(context);
     return info != null && info.isConnected();
 }
/**
 * 獲取活動(dòng)網(wǎng)絡(luò)信息
 *
 * @param context 上下文
 * @return NetworkInfo
 */
private static NetworkInfo getActiveNetworkInfo(Context context) {
    ConnectivityManager cm = (ConnectivityManager) context
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    return cm.getActiveNetworkInfo();
}
  • 附上本文所示例用的攔截器
/**
 * <pre>
 *     author: Chestnut
 *     blog  : http://www.reibang.com/u/a0206b5f4526
 *     time  : 2018/2/4 22:16
 *     desc  :
 *     thanks To:
 *     dependent on:
 *     update log:
 * </pre>
 */
public class XInterceptor {

    /**
     * 自定義的握恳,重試N次的攔截器
     * 通過:addInterceptor 設(shè)置
     */
    public static class Retry implements Interceptor {

        public int maxRetry;//最大重試次數(shù)
        private int retryNum = 0;//假如設(shè)置為3次重試的話,則最大可能請求4次(默認(rèn)1次+3次重試)

        public Retry(int maxRetry) {
            this.maxRetry = maxRetry;
        }

        @Override
        public Response intercept(@NonNull Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            Log.i("Retry","num:"+retryNum);
            while (!response.isSuccessful() && retryNum < maxRetry) {
                retryNum++;
                Log.i("Retry","num:"+retryNum);
                response = chain.proceed(request);
            }
            return response;
        }
    }

    /**
     * 設(shè)置沒有網(wǎng)絡(luò)的情況下捺僻,
     *  的緩存時(shí)間
     *  通過:addInterceptor 設(shè)置
     */
    public static class CommonNoNetCache implements Interceptor {

        private int maxCacheTimeSecond = 0;
        private Context applicationContext;

        public CommonNoNetCache(int maxCacheTimeSecond, Context applicationContext) {
            this.maxCacheTimeSecond = maxCacheTimeSecond;
            this.applicationContext = applicationContext;
        }

        @Override
        public Response intercept(@NonNull Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetworkUtils.isConnected(applicationContext)) {
                CacheControl tempCacheControl = new CacheControl.Builder()
                        .onlyIfCached()
                        .maxStale(maxCacheTimeSecond, TimeUnit.SECONDS)
                        .build();
                request = request.newBuilder()
                        .cacheControl(tempCacheControl)
                        .build();
            }
            return chain.proceed(request);
        }
    }

    /**
     * 設(shè)置在有網(wǎng)絡(luò)的情況下的緩存時(shí)間
     *  在有網(wǎng)絡(luò)的時(shí)候乡洼,會(huì)優(yōu)先獲取緩存
     * 通過:addNetworkInterceptor 設(shè)置
     */
    public static class CommonNetCache implements Interceptor {

        private int maxCacheTimeSecond = 0;

        public CommonNetCache(int maxCacheTimeSecond) {
            this.maxCacheTimeSecond = maxCacheTimeSecond;
        }

        @Override
        public Response intercept(@NonNull Chain chain) throws IOException {
            Request request = chain.request();
            Response originalResponse = chain.proceed(request);
            return originalResponse.newBuilder()
                    .removeHeader("Pragma")// 清除頭信息,因?yàn)榉?wù)器如果不支持匕坯,會(huì)返回一些干擾信息束昵,不清除下面無法生效
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, max-age=" + maxCacheTimeSecond)
                    .build();
        }
    }

    /**
     * 設(shè)置一個(gè)日志打印攔截器
     * 通過:addInterceptor 設(shè)置
     */
    public static class CommonLog implements Interceptor {

        //統(tǒng)一的日志輸出控制,可以構(gòu)造方法傳入醒颖,統(tǒng)一控制日志
        private boolean logOpen = true;
        //log的日志TAG
        private String logTag = "CommonLog";

        public CommonLog() {}

        public CommonLog(boolean logOpen) {
            this.logOpen = logOpen;
        }

        public CommonLog(String logTag) {
            this.logTag = logTag;
        }

        public CommonLog(boolean logOpen, String logTag) {
            this.logOpen = logOpen;
            this.logTag = logTag;
        }

        @Override
        public Response intercept(@NonNull Chain chain) throws IOException {

            Request request = chain.request();
            long t1 = System.currentTimeMillis();//請求發(fā)起的時(shí)間
            Response response = chain.proceed(request);
            long t2 = System.currentTimeMillis();//收到響應(yīng)的時(shí)間

            if (logOpen) {
                //這里不能直接使用response.body().string()的方式輸出日志
                //因?yàn)閞esponse.body().string()之后妻怎,response中的流會(huì)被關(guān)閉壳炎,程序會(huì)報(bào)錯(cuò)泞歉,我們需要?jiǎng)?chuàng)建出一
                //個(gè)新的response給應(yīng)用層處理
                ResponseBody responseBody = response.peekBody(1024 * 1024);
                Log.i(logTag, response.request().url() + " , use-timeMs: " + (t2 - t1) + " , data: " + responseBody.string());
            }

            return response;
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逼侦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子腰耙,更是在濱河造成了極大的恐慌榛丢,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挺庞,死亡現(xiàn)場離奇詭異晰赞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)选侨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門掖鱼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人援制,你說我怎么就攤上這事戏挡。” “怎么了晨仑?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵褐墅,是天一觀的道長。 經(jīng)常有香客問我洪己,道長妥凳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任答捕,我火速辦了婚禮逝钥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拱镐。我一直安慰自己晌缘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布痢站。 她就那樣靜靜地躺著磷箕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阵难。 梳的紋絲不亂的頭發(fā)上岳枷,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音呜叫,去河邊找鬼空繁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛朱庆,可吹牛的內(nèi)容都是我干的盛泡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼娱颊,長吁一口氣:“原來是場噩夢啊……” “哼傲诵!你這毒婦竟也來了凯砍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤拴竹,失蹤者是張志新(化名)和其女友劉穎悟衩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栓拜,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡座泳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幕与。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挑势。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啦鸣,靈堂內(nèi)的尸體忽然破棺而出薛耻,到底是詐尸還是另有隱情,我是刑警寧澤赏陵,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布饼齿,位于F島的核電站,受9級(jí)特大地震影響蝙搔,放射性物質(zhì)發(fā)生泄漏缕溉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一吃型、第九天 我趴在偏房一處隱蔽的房頂上張望证鸥。 院中可真熱鬧,春花似錦勤晚、人聲如沸枉层。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸟蜡。三九已至,卻和暖如春挺邀,著一層夾襖步出監(jiān)牢的瞬間揉忘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工端铛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泣矛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓禾蚕,卻偏偏與公主長得像您朽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子换淆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評論 25 707
  • 又是一年中秋佳節(jié)哗总,祝各位中秋節(jié)快樂几颜。 今天我們來聊聊這個(gè)最近很火的網(wǎng)絡(luò)請求庫retrofit,在此基礎(chǔ)上會(huì)延伸出一...
    涅槃1992閱讀 7,774評論 13 133
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理魂奥,服務(wù)發(fā)現(xiàn),斷路器易猫,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 圖/網(wǎng)絡(luò)1. 我一特好的朋友D准颓,晚上約我一起擼串兒哈蝇,兩瓶酒下肚,D郁悶的和我說攘已,他十幾年的一個(gè)哥們最近做的事情讓他...
    戴存在閱讀 3,838評論 6 1
  • ***: 考英語的時(shí)候抑制不住的興奮炮赦,心狂跳,因?yàn)轳R上要解放了样勃,很激動(dòng)吠勘,激動(dòng)到我都想吐了。 可是峡眶,考完之后的興奮遠(yuǎn)...
    水玉閱讀 164評論 0 1