一些你可能須要的okhttp實現

http://www.javashuo.com/article/p-mctnesmx-cs.htmlphp

今天分享一些我在項目中使用到的okhttp實現,由簡至難。(如下內容均在okhttp3.4.1下正常使用)css

1.okhttp日誌打印

這個就簡單了,一個工具類。先上代碼:html

public class LoggingInterceptor implements Interceptor {

    @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request request = chain.request(); KLog.d(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); long t1 = System.nanoTime(); okhttp3.Response response = chain.proceed(chain.request()); long t2 = System.nanoTime(); KLog.d(String.format(Locale.getDefault(), "Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); okhttp3.MediaType mediaType = response.body().contentType(); String content = response.body().string(); KLog.json(content); return response.newBuilder() .body(okhttp3.ResponseBody.create(mediaType, content)) .build(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

這裏我爲了打印清晰使用了KLog用來打印Log。java

使用方法:git

OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new LogInterceptor()) .build();
  • 1
  • 2
  • 3

打印例子: 
這裏寫圖片描述github

固然了,你也可使用官方提供的Logging Interceptor,也是很是方便。具體參見連接數據庫

2.okhttp網絡緩存

(1)首先設置Cachejson

private static File cacheFile = new File(context.getCacheDir(), "Test"); private static Cache cache = new Cache(cacheFile, 1024 * 1024 * 10); private static OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .build();
  • 1
  • 2
  • 3
  • 4
  • 5

這裏使用getCacheDir()來做爲緩存文件的存放路徑(/data/data/包名/cache) ,若是你想看到緩存文件能夠臨時使用 getExternalCacheDir()(/sdcard/Android/data/包名/cache)。瀏覽器

(2)若是咱們的服務器支持緩存,那麼Response中的頭文件中會有Cache-Control: max-age=xxx這樣的字段。以下圖:緩存

這裏寫圖片描述

這裏的public意思是能夠無條件的緩存該響應,max-age是你緩存的最大存放的時間。好比你設置了6分鐘的最大緩存時間,那麼6分鐘內他會讀取緩存,但超過這個時間則緩存失效。具體的響應標頭你們能夠自行查詢。

(3)若是咱們的服務器不支持緩存,也就是響應頭沒有對應字段,那麼咱們可使用網絡攔截器實現:

public class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!isNetworkConnected()) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response originalResponse = chain.proceed(request); if (isNetworkConnected()) { //有網的時候讀接口上的@Headers裏的配置,你能夠在這裏進行統一的設置(注掉部分) String cacheControl = request.cacheControl().toString(); return originalResponse.newBuilder() .header("Cache-Control", cacheControl) //.header("Cache-Control", "max-age=3600") .removeHeader("Pragma") // 清除頭信息,由於服務器若是不支持,會返回一些干擾信息,不清除下面沒法生效 .build(); } else { int maxAge= 60 * 60; return originalResponse.newBuilder() .header("Cache-Control", "public, only-if-cached, max-age=" + maxAge) .removeHeader("Pragma") .build(); } } private boolean isNetworkConnected() { ConnectivityManager connectivity = (ConnectivityManager) MyApplication.getInstance() .getSystemService(Context.CONNECTIVITY_SERVICE); if (null != connectivity){ NetworkInfo info = connectivity.getActiveNetworkInfo(); if (null != info && info.isConnected()){ if (info.getState() == NetworkInfo.State.CONNECTED){ return true; } } } return false; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

設置攔截器:

private static OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(new CacheInterceptor()) .addNetworkInterceptor(new CacheInterceptor()) .build();
  • 1
  • 2
  • 3
  • 4
  • 5

注意:addInterceptoraddNetworkInterceptor 須要同時設置。 這二者的區別能夠參考Interceptors 攔截器。我只說一下效果,若是你只是想實如今線緩存,那麼能夠只添加網絡攔截器,若是隻想實現離線緩存,可使用只添加應用攔截器。二者都添加,就不用我說了吧。

若是在攔截器中統一配置,則全部的請求都會緩存。可是在實際開發中有些接口須要保證數據的實時性,那麼咱們就不能統一配置,這時能夠這樣:

@Headers("Cache-Control: public, max-age=時間秒數") @GET("weilu/test")  Observable<Test> getData();
  • 1
  • 2
  • 3

我本身找了一些配置,你們能夠根據我的需求使用:

1.不須要緩存:Cache-Control: no-cacheCache-Control: max-age=0

2.若是想先顯示數據,在請求。(相似於微博等):Cache-Control: only-if-cached

經過以上配置後經過攔截器中的request.cacheControl().toString() 就能夠獲取到咱們配置的Cache-Control頭文件,實現對應的緩存策略。

測試一下:

1.首先我設置@Headers("Cache-Control: public,max-age=30")

2.30秒有效期內請求第一次

這裏寫圖片描述

3.30內請求第二次(在線緩存)

這裏寫圖片描述

你可能會說沒有什麼不一樣,其實仔細看看就會發現,兩次的請求響應時間分別爲85.5ms和1.5ms,這說明是直接讀取的緩存。同時咱們能夠查看Monitors中的Network發現並無請求網絡,也一樣說明使用的是緩存。

從這裏其實也就說明了添加緩存的好處:1.下降了請求的延遲。2.下降網絡的頻繁請求。

3.30超出後再請求一次結果與第二步一致。

這裏寫圖片描述

緩存失效,從新請求。

4.關閉網絡後超過30s請求一次(離線緩存)

這裏寫圖片描述

這時發現警告響應失效。這說明已經超過30秒,若是沒有超出則沒有警告。你們能夠自行嘗試。

最後感興趣的能夠去咱們設置的緩存目錄查看一下緩存文件,你必定會有新的發現。

3.okhttp實現token過時刷新

(1)使用攔截器實現,這個具體參看這篇文章,我本身沒有試過,提供出來給你們拓展一下思路。

(2)使用RxJava的操做符retryWhen 實現。這個是我在項目中使用的方法,也是重點說明的方法。

首先說明一下咱們這邊的狀況,咱們的登陸驗證使用的是登錄成功後包含在響應頭文件中的Set-Cookie,這其中有sessionId,凡是須要登陸操做的只需請求時帶上他便可。

登陸超時時服務器會返回code爲401的json來告知咱們登陸超時。那麼其實就很簡單了,只須要判斷code是否爲401,是則登陸一下保存新的Cookie,在從新請求一下以前的操做就好了。

下來我來實現如下,至於其餘的狀況其實大同小異。

首先咱們來講明一下retryWhenretryWhen操做符是在源Observable出現錯誤或者異常時,經過回調另外一個Observable來判斷是否從新嘗試執行源Observable的邏輯,若是這個Observable沒有錯誤或者異常出現,則就會從新嘗試執行源Observable的邏輯,不然就會直接回調執行訂閱者的onError方法。

看了上面的概念是否是有點繞,其實意思就和他的名字同樣「重試」,而觸發的條件是有了「錯誤」,錯誤解決,那麼再試一次。其實這就和咱們須要解決的問題同樣,登陸失敗(觸發)–> 從新登陸(解決) –> 重試。

首先定義一個父類。

public class TimeOut { private int timeout; public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

自定義一個異常,能夠不用實現

public class TimeOutException extends Exception{ }
  • 1
  • 2

簡單封裝了一下,這樣不用重複書寫超時判斷。

protected Subscription subscription; public <B extends TimeOut> void toSubscribe(Observable<B> o, Subscriber<B> s) { unSubscribe(); subscription = o.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .flatMap(new Func1<B, Observable<B>>() { @Override public Observable<B> call(B timeout) { if(timeout.getTimeout() == 401){ return Observable.error(new TimeOutException()); //超時,則觸發retryWhen } return Observable.just(timeout); } }) .retryWhen(new TimeOutRetry()) //<---- .subscribe(s); } public void unSubscribe(){ if (subscription != null) subscription.unsubscribe(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

TimeOutRetry 類:

public class TimeOutRetry implements Func1<Observable<? extends Throwable>, Observable<?>>{ @Override public Observable<?> call(Observable<? extends Throwable> observable) { return observable.flatMap(new Func1<Throwable, Observable<?>>() { @Override public Observable<?> call(Throwable throwable) { if (throwable instanceof TimeOutException) { KLog.e("-----重登進入-----"); String name = 獲取用戶名; String pwd = 獲取密碼; if(TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)){ KLog.e("--- 超時重登未完成 ---"); return Observable.error(throwable); } return mApi().login(name, pwd) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .unsubscribeOn(Schedulers.io()) .doOnNext(new Action1<Login>() { @Override public void call(Login login) { KLog.e("--- 登陸完成 ---"); } }); } return Observable.error(throwable); // 其餘異常直接結束 } }); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

使用:

public void test(){ toSubscribe(mApi.getData(), new Subscriber<Test>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Test test) { } }); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意這裏的Test類須要繼承TimeOut。

結果如圖:

這裏寫圖片描述

這裏寫圖片描述

看完這麼多,咱們能夠看到這些功能均可以利用攔截器去實現,可見它的強大之處。(補充:說到了攔截器,忽然想到了Facebook出的一個強大的Android調試工具stetho,該工具讓你能夠在谷歌瀏覽器查看App的佈局,preference,網絡請求,數據庫,一切都是可視化的操做,不須要root你的設備。其中也支持okhttp,使用方法就是添加一個網絡攔截器。)還有一些實現,好比https的訪問,cookie的同步問題我就不一一去說了,咱們能夠參考okhttputilsokhttp-OkGo 去實現。好了,本篇到此結束!若是文章中有錯誤的地方但願指出,多多交流。最後喜歡的點個贊哈!

4.參考

  1. Interceptors

  2. 使用Retrofit和Okhttp實現網絡緩存

  3. Okhttp緩存淺析

  4. Recipes

  5. Rxjava+Retrofit 實現全局過時 token 自動刷新


ps:發現文章中圖片看的不是很清晰,你們能夠右鍵–>打開圖片,來查看大圖。

掃碼向博主提問

qq_17766199

非學,無以至疑;非問,無以廣識
  • 擅長領域:
  • Android
  • 單元測試
相關文章
相關標籤/搜索