http://www.javashuo.com/article/p-mctnesmx-cs.htmlphp
今天分享一些我在項目中使用到的okhttp實現,由簡至難。(如下內容均在okhttp3.4.1下正常使用)css
這個就簡單了,一個工具類。先上代碼: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(); } }
這裏我爲了打印清晰使用了KLog用來打印Log。java
使用方法:git
OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new LogInterceptor()) .build();
打印例子:
github
固然了,你也可使用官方提供的Logging Interceptor,也是很是方便。具體參見連接數據庫
(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();
這裏使用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; } }
設置攔截器:
private static OkHttpClient okHttpClient = new OkHttpClient.Builder() .cache(cache) .addInterceptor(new CacheInterceptor()) .addNetworkInterceptor(new CacheInterceptor()) .build();
注意:addInterceptor
和addNetworkInterceptor
須要同時設置。 這二者的區別能夠參考Interceptors 攔截器。我只說一下效果,若是你只是想實如今線緩存,那麼能夠只添加網絡攔截器,若是隻想實現離線緩存,可使用只添加應用攔截器。二者都添加,就不用我說了吧。
若是在攔截器中統一配置,則全部的請求都會緩存。可是在實際開發中有些接口須要保證數據的實時性,那麼咱們就不能統一配置,這時能夠這樣:
@Headers("Cache-Control: public, max-age=時間秒數") @GET("weilu/test") Observable<Test> getData();
我本身找了一些配置,你們能夠根據我的需求使用:
1.不須要緩存:Cache-Control: no-cache
或Cache-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秒,若是沒有超出則沒有警告。你們能夠自行嘗試。
最後感興趣的能夠去咱們設置的緩存目錄查看一下緩存文件,你必定會有新的發現。
(1)使用攔截器實現,這個具體參看這篇文章,我本身沒有試過,提供出來給你們拓展一下思路。
(2)使用RxJava的操做符retryWhen
實現。這個是我在項目中使用的方法,也是重點說明的方法。
首先說明一下咱們這邊的狀況,咱們的登陸驗證使用的是登錄成功後包含在響應頭文件中的Set-Cookie,這其中有sessionId,凡是須要登陸操做的只需請求時帶上他便可。
登陸超時時服務器會返回code爲401的json來告知咱們登陸超時。那麼其實就很簡單了,只須要判斷code是否爲401,是則登陸一下保存新的Cookie,在從新請求一下以前的操做就好了。
下來我來實現如下,至於其餘的狀況其實大同小異。
首先咱們來講明一下retryWhen
,retryWhen
操做符是在源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; } }
自定義一個異常,能夠不用實現
public class TimeOutException extends Exception{ }
簡單封裝了一下,這樣不用重複書寫超時判斷。
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(); }
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); // 其餘異常直接結束 } }); } }
使用:
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) { } }); }
注意這裏的Test類須要繼承TimeOut。
結果如圖:
看完這麼多,咱們能夠看到這些功能均可以利用攔截器去實現,可見它的強大之處。(補充:說到了攔截器,忽然想到了Facebook出的一個強大的Android調試工具stetho,該工具讓你能夠在谷歌瀏覽器查看App的佈局,preference,網絡請求,數據庫,一切都是可視化的操做,不須要root你的設備。其中也支持okhttp,使用方法就是添加一個網絡攔截器。)還有一些實現,好比https的訪問,cookie的同步問題我就不一一去說了,咱們能夠參考okhttputils與okhttp-OkGo 去實現。好了,本篇到此結束!若是文章中有錯誤的地方但願指出,多多交流。最後喜歡的點個贊哈!
ps:發現文章中圖片看的不是很清晰,你們能夠右鍵–>打開圖片,來查看大圖。
掃碼向博主提問
qq_17766199