RxRetrofit-終極封裝-深入淺出&網路請求

wzgiceman發表於2016-12-23

背景:

CSDN部落格釋出了一系列的RxJava+Retrofit+OkHttp深入淺出-終極封裝是不是很眼熟,是的還是它,不過是不同的配方。之前發出後收到很多朋友的關注,原本只是自己學習後的一些經驗總結,但是有同學運用到實戰當中,這讓我很惶恐,所有後續一直更新了很多次版本,有些地方難免有所變動導致之前的部落格有所出入,正好最近受到掘金邀請內測部落格,所以決定重新寫一版,按照最後迭代完成的封裝詳細的講述一遍,歡迎大家關注!

注意:由於本章的特殊性,後續文章比較長而且複雜,涉及內容也很多,所以大家準備好茶水,前方高能預警。

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


封裝成果

封裝完以後,具有如下功能:

    1.Retrofit+Rxjava+okhttp基本使用方法
    2.統一處理請求資料格式
    3.統一的ProgressDialog和回撥Subscriber處理
    4.取消http請求
    5.預處理http請求
    6.返回資料的統一判斷
    7.失敗後的retry封裝處理
    8.RxLifecycle管理生命週期,防止洩露複製程式碼

實現效果:

RxRetrofit-終極封裝-深入淺出&網路請求


具體使用

封裝後http請求程式碼如下

//    完美封裝簡化版
    private void simpleDo() {
        SubjectPost postEntity = new SubjectPost(simpleOnNextListener,this);
        postEntity.setAll(true);
        HttpManager manager = HttpManager.getInstance();
        manager.doHttpDeal(postEntity);
    }

    //   回撥一一對應
    HttpOnNextListener simpleOnNextListener = new HttpOnNextListener<List<Subject>>() {
        @Override
        public void onNext(List<Subject> subjects) {
            tvMsg.setText("已封裝:\n" + subjects.toString());
        }

        /*使用者主動呼叫,預設是不需要覆寫該方法*/
        @Override
        public void onError(Throwable e) {
            super.onError(e);
            tvMsg.setText("失敗:\n" + e.toString());
        }
    };複製程式碼

是不是很簡單?你可能說這還簡單,好我們們對比一下正常使用Retrofit的方法

/**  
    * Retrofit加入rxjava實現http請求  
    */  
   private void onButton9Click() {  
       //手動建立一個OkHttpClient並設定超時時間  
       okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder();  
       builder.connectTimeout(5, TimeUnit.SECONDS);  

       Retrofit retrofit = new Retrofit.Builder()  
               .client(builder.build())  
               .addConverterFactory(GsonConverterFactory.create())  
               .addCallAdapterFactory(RxJavaCallAdapterFactory.create())  
               .baseUrl(HttpManager.BASE_URL)  
               .build();  

/        載入框  
       final ProgressDialog pd = new ProgressDialog(this);  

       HttpService apiService = retrofit.create(HttpService.class);  
       Observable<RetrofitEntity> observable = apiService.getAllVedioBy(true);  
       observable.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())  
               .subscribe(  
                       new Subscriber<RetrofitEntity>() {  
                           @Override  
                           public void onCompleted() {  
                               if (pd != null && pd.isShowing()) {  
                                   pd.dismiss();  
                               }  
                           }  

                           @Override  
                           public void onError(Throwable e) {  
                               if (pd != null && pd.isShowing()) {  
                                   pd.dismiss();  
                               }  
                           }  

                           @Override  
                           public void onNext(RetrofitEntity retrofitEntity) {  
                               tvMsg.setText("無封裝:\n" + retrofitEntity.getData().toString());  
                           }  

                           @Override  
                           public void onStart() {  
                               super.onStart();  
                               pd.show();  
                           }  
                       }  

               );  
   }複製程式碼

可能你發現確是程式碼有點多,但是更加可怕的是,如果你一個activity或者fragment中多次需要http請求,你需要多次重複的寫回撥處理(一個回到就有4個方法呀!!!!反正我是忍受不了),而且以上處理還沒有做過多的判斷和錯誤校驗就如此複雜!~好了介紹完了,開始我們們的優化之路吧!


專案結構:

RxRetrofit-終極封裝-深入淺出&網路請求


RxJava

如果你對RxJava不瞭解,好吧騷年趕快學學吧,不然真會out了,下面給出博主當初學習RxJava的一些資源:


Retrofit

我們家今天的主角來了,我們們也深入淺出一下了解下Retrofit使用,前方高能,如果你是深度Retrofit選手請直接跳過本節!!!

1.首先確保在AndroidManifest.xml中請求了網路許可權

<uses-permission android:name="android.permission.INTERNET"/>複製程式碼

2.在app/build.gradle新增引用

  /*rx-android-java*/
    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    compile 'com.trello:rxlifecycle:1.0'
    compile 'com.trello:rxlifecycle-components:1.0'
    /*rotrofit*/
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0'
    compile 'com.google.code.gson:gson:2.8.0'複製程式碼

3.常用註解

這裡介紹一些常用的註解的使用

  • @Query@QueryMap:用於Http Get請求傳遞引數

  • @Field:用於Post方式傳遞引數,需要在請求介面方法上新增@FormUrlEncoded,即以表單的方式傳遞引數

  • @Body:用於Post,根據轉換方式將例項物件轉化為對應字串傳遞引數.比如Retrofit新增GsonConverterFactory則是將body轉化為gson字串進行傳遞

  • @Path:用於URL上佔位符

  • @Part:配合@Multipart使用,一般用於檔案上傳

  • @Header:新增http header

  • @Headers:跟@Header作用一樣,只是使用方式不一樣,@Header是作為請求方法的引數傳入,@Headers是以固定方式直接新增到請求方法上

ReTrofit基本使用:

首先給定一個測試介面文件,後面的部落格中我們都是用這個介面除錯

/**  
 * @api    videoLink    50音圖視訊連結  
 * @url    http://www.izaodao.com/Api/AppFiftyToneGraph/videoLink  
 * @method post  
 * @param  once_no bool(選填,ture無連結) 一次性獲取下載地址  
 * @return json array(  
 * ret:1成功,2失敗  
 * msg:資訊  
 * data:{  
 *       name:視訊名稱  
 *       title:標題  
 * }  
 )複製程式碼

1.初始化retrofit

要向一個api傳送我們的網路請求 ,我們需要使用Retrofit builder類並指定servicebase URL(通常情況下就是域名)。

String BASE_URL = " http://www.izaodao.com/Api/"  
    Retrofit retrofit = new Retrofit.Builder()  
            .baseUrl(BASE_URL)  
            .addConverterFactory(GsonConverterFactory.create())  
            .build();複製程式碼

2.設定介面service

注意到每個endpoint 都指定了一個關於HTTP(GET, POST, 等等。) 方法的註解以及用於分發網路呼叫的方法。而且這些方法的引數也可以有特殊的註解。

/**  
 * 介面地址  
 * Created by WZG on 2016/7/16.  
 */  
public interface MyApiEndpointInterface {  
    @POST("AppFiftyToneGraph/videoLink")  
    Call<RetrofitEntity> getAllVedio(@Body boolean once_no)             
}複製程式碼

3.得到call然後同步處理處理回撥:

MyApiEndpointInterface apiService = retrofit.create(MyApiEndpointInterface.class);  
Call<RetrofitEntity> call = apiService.getAllVedio(true);  
call.enqueue(new Callback<RetrofitEntity>() {  
    @Override  
    public void onResponse(Response<RetrofitEntity> response, Retrofit retrofit) {  
        RetrofitEntity entity = response.body();  
        Log.i("tag", "onResponse----->" + entity.getMsg());  
    }  

    @Override  
    public void onFailure(Throwable t) {  
        Log.i("tag", "onFailure----->" + t.toString());  

    }  
});複製程式碼

這就是簡單的Retrofit使用步驟,接下來我們結合RxJava講述

ReTrofit+Rxjava基本使用

對比之前的Retrofit使用

1.在於我們需要修改service介面返回資訊我們需要返回一個Observable物件

@POST("AppFiftyToneGraph/videoLink")  
Observable<RetrofitEntity> getAllVedioBy(@Body boolean once_no);複製程式碼

2.然後初始化Retrofit需要新增對Rxjava的適配,注意一定要retrofit2才有這個功能哦

Retrofit retrofit = new Retrofit.Builder()  
                .client(builder.build())  
                .addConverterFactory(GsonConverterFactory.create())  
               .addCallAdapterFactory(RxJavaCallAdapterFactory.create())  
                .baseUrl(HttpManager.BASE_URL)  
                .build();複製程式碼

3.回撥通過RxJava處理

HttpService apiService = retrofit.create(HttpService.class);  
       Observable<RetrofitEntity> observable = apiService.getAllVedioBy(true);  
       observable.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())  
               .subscribe(  
                       new Subscriber<RetrofitEntity>() {  
                           @Override  
                           public void onCompleted() {  
                           }  

                           @Override  
                           public void onError(Throwable e) {                                
                           }  

                           @Override  
                           public void onNext(RetrofitEntity retrofitEntity) {  
                               tvMsg.setText("無封裝:\n" + retrofitEntity.getData().toString());  
                           }                            
                       }  

               );複製程式碼

簡單的RxJava集合Retrofit的使用就介紹完了,同樣的可以發現使用起來很多重複性的程式碼,而且使用也不是那麼簡單,所以才有了下面的封裝


ReTrofit+Rxjava進階封裝之路

先來一張流程圖壓壓驚

RxRetrofit-終極封裝-深入淺出&網路請求


請求資料封裝

1.引數

首先需要封裝的使我們的資料類,在資料類中需要封裝請求中用到的相關資料的設定,比如請求引數、方法、載入框顯示設定等等

public abstract class BaseApi<T> implements Func1<BaseResultEntity<T>, T> {
    //rx生命週期管理
    private SoftReference<RxAppCompatActivity> rxAppCompatActivity;
    /*回撥*/
    private SoftReference<HttpOnNextListener> listener;
    /*是否能取消載入框*/
    private boolean cancel;
    /*是否顯示載入框*/
    private boolean showProgress;
    /*是否需要快取處理*/
    private boolean cache;
    /*基礎url*/
    private  String baseUrl="http://www.izaodao.com/Api/";
    /*方法-如果需要快取必須設定這個引數;不需要不用設定*/
    private String mothed;
    /*超時時間-預設6秒*/
    private int connectionTime = 6;
    /*有網情況下的本地快取時間預設60秒*/
    private int cookieNetWorkTime=60;
    /*無網路的情況下本地快取時間預設30天*/
    private int cookieNoNetWorkTime=24*60*60*30;
}複製程式碼

註釋很詳細,這裡不具體描述了,由於這裡是最後封裝完成以後的程式碼,所以有些內容本章還會部分不會涉及,因為功能太多,還是按照一開始的部落格章節講解。

2.抽象api介面

    /**
     * 設定引數
     *
     * @param retrofit
     * @return
     */
    public abstract Observable getObservable(Retrofit retrofit);複製程式碼

通過子類也即是我們的具體api介面,通過getObservable實現service中定義的介面方法,例如:

public class SubjectPostApi extends BaseApi {
     xxxxxxx
     xxxxxxx

 @Override
    public Observable getObservable(Retrofit retrofit) {
        HttpPostService service = retrofit.create(HttpPostService.class);
        return service.getAllVedioBys(isAll());
    }
}複製程式碼

通過傳入的Retrofit物件,可以隨意切換挑選Service物件,得到定義的註解方法,初始完成以後返回Observable物件。

3.結果判斷

這裡結合RxJavamap方法在伺服器返回資料中,統一處理資料處理,所以BaseApi<T> implements Func1<BaseResultEntity<T>, T>,後邊結合結果處理連結起來使用

    @Override
    public T call(BaseResultEntity<T> httpResult) {
        if (httpResult.getRet() == 0) {
            throw new HttpTimeException(httpResult.getMsg());
        }
        return httpResult.getData();
    }複製程式碼

由於測試介面,也是當前我們公司介面都是有統一規則的,想必大家都有這樣的介面規則,所以才有這裡的統一判斷,規則如下:

 * ret:1成功,2失敗  
 * msg:資訊  
 * data:{  
 *       name:視訊名稱  
 *       title:標題  
 * }複製程式碼

其實上面的介面文件中就介紹了,統一先通過ret判斷,失敗顯示msg資訊,data是成功後的資料也就是使用者關心的資料,所以可封裝一個結果物件BaseResultEntity.

4.結果資料

/**
 * 回撥資訊統一封裝類
 * Created by WZG on 2016/7/16.
 */
public class BaseResultEntity<T> {
    //  判斷標示
    private int ret;
    //    提示資訊
    private String msg;
    //顯示資料(使用者需要關心的資料)
    private T data;


    xxxxx  get-set  xxxxx
}複製程式碼

這裡結合BaseApiFunc1判斷,失敗直接丟擲一個異常,交個RxJavaonError處理,成功則將使用者關心的資料傳給Gson解析返回

5.泛型傳遞

BaseResultEntity<T>中的泛型T也就是我們所關心的回撥資料,同樣也是Gson最後解析返回的資料,傳遞的過程根節點是通過定義service方法是給定的,例如:

public interface HttpPostService {
    @POST("AppFiftyToneGraph/videoLink")
    Call<RetrofitEntity> getAllVedio(@Body boolean once_no);
}複製程式碼

其中的RetrofitEntity就是使用者關心的資料類,通過泛型傳遞給最後的介面。

6.強調

很多兄弟通過QQ群反饋給我說,使用一個介面需要寫一個對應的api類繼承BaseApi是不是很麻煩,我這裡強調一下,這樣封裝是為了將一個Api介面作為一個物件去封裝,個人覺得有必要封裝成一個類,在日後工程日益增加介面隨著增加的同時,物件的做法更加有利於查詢介面和修改介面有利於迭代。


操作類封裝

1初始物件

首先初始化一個單利方便HttpManager請求;這裡用了volatile的物件,不懂的同學可以參考我的另一篇部落格

你真的會寫單例嗎

    private volatile static HttpManager INSTANCE;

    //構造方法私有
    private HttpManager() {
    }

    //獲取單例
    public static HttpManager getInstance() {
        if (INSTANCE == null) {
            synchronized (HttpManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new HttpManager();
                }
            }
        }
        return INSTANCE;
    }複製程式碼

2介面處理和回撥處理:

 /**
     * 處理http請求
     *
     * @param basePar 封裝的請求資料
     */
    public void doHttpDeal(BaseApi basePar) {
        //手動建立一個OkHttpClient並設定超時時間快取等設定
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(basePar.getConnectionTime(), TimeUnit.SECONDS);
        builder.addInterceptor(new CookieInterceptor(basePar.isCache()));

        /*建立retrofit物件*/
        Retrofit retrofit = new Retrofit.Builder()
                .client(builder.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(basePar.getBaseUrl())
                .build();


        /*rx處理*/
        ProgressSubscriber subscriber = new ProgressSubscriber(basePar);
        Observable observable = basePar.getObservable(retrofit)
                /*失敗後的retry配置*/
                .retryWhen(new RetryWhenNetworkException())
                /*生命週期管理*/
                .compose(basePar.getRxAppCompatActivity().bindToLifecycle())
                /*http請求執行緒*/
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                /*回撥執行緒*/
                .observeOn(AndroidSchedulers.mainThread())
                /*結果判斷*/
                .map(basePar);

        /*資料回撥*/
        observable.subscribe(subscriber);
    }複製程式碼

首先通過api介面類BaseApi的實現類中資料初始化OkHttpClientRetrofit物件,其中包含了url,超時等,接著通過BaseApi的抽象方法getObservable得到Observable物件,得到Observable物件以後,我們就能隨意的切換現成來處理,整個請求通過compose設定的rxlifecycle來管理生命週期,所以不會溢位和洩露無需任何擔心,最後再伺服器資料返回時,通過map判斷結果,剔除錯誤資訊,成功以後返回到自定義的ProgressSubscriber物件中,所以接下來封裝ProgressSubscriber物件。


ProgressSubscriber封裝

ProgressSubscriber其實是繼承於Subscriber,封裝的方法無非是對Subscriber的回撥方法的封裝

  • onStart():開始
  • onCompleted():結束
  • onError(Throwable e):錯誤
  • onNext(T t):成功

1.請求載入框

http請求都伴隨著載入框的使用,所以這裡需要在onStart()使用前初始一個載入框,這裡簡單的用ProgressDialog代替

/**
 * 用於在Http請求開始時,自動顯示一個ProgressDialog
 * 在Http請求結束是,關閉ProgressDialog
 * 呼叫者自己對請求資料進行處理
 * Created by WZG on 2016/7/16.
 */
public class ProgressSubscriber<T> extends Subscriber<T> {
    /*是否彈框*/
    private boolean showPorgress = true;
    /* 軟引用回撥介面*/
    private SoftReference<HttpOnNextListener> mSubscriberOnNextListener;
    /*軟引用反正記憶體洩露*/
    private SoftReference<RxAppCompatActivity> mActivity;
    /*載入框可自己定義*/
    private ProgressDialog pd;
    /*請求資料*/
    private BaseApi api;


    /**
     * 構造
     *
     * @param api
     */
    public ProgressSubscriber(BaseApi api) {
        this.api = api;
        this.mSubscriberOnNextListener = api.getListener();
        this.mActivity = new SoftReference<>(api.getRxAppCompatActivity());
        setShowPorgress(api.isShowProgress());
        if (api.isShowProgress()) {
            initProgressDialog(api.isCancel());
        }
    }


    /**
     * 初始化載入框
     */
    private void initProgressDialog(boolean cancel) {
        Context context = mActivity.get();
        if (pd == null && context != null) {
            pd = new ProgressDialog(context);
            pd.setCancelable(cancel);
            if (cancel) {
                pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialogInterface) {
                        onCancelProgress();
                    }
                });
            }
        }
    }


    /**
     * 顯示載入框
     */
    private void showProgressDialog() {
        if (!isShowPorgress()) return;
        Context context = mActivity.get();
        if (pd == null || context == null) return;
        if (!pd.isShowing()) {
            pd.show();
        }
    }


    /**
     * 隱藏
     */
    private void dismissProgressDialog() {
        if (!isShowPorgress()) return;
        if (pd != null && pd.isShowing()) {
            pd.dismiss();
        }
    }
}複製程式碼

由於progress的特殊性,需要指定content而且不能是Application所以這裡傳遞一個RxAppCompatActivity,而同時上面的HttpManager同樣需要,所以這裡統一還是按照BaseApi傳遞過來,使用軟引用的方式避免洩露。剩下的無非是初始化,顯示和關閉方法,可以詳細看程式碼。

2.onStart()實現

onStart()中需要呼叫載入框,然後這裡還有網路快取的邏輯,後面會單獨講解,現在先忽略它的存在。

 /**
     * 訂閱開始時呼叫
     * 顯示ProgressDialog
     */
    @Override
    public void onStart() {
        showProgressDialog();
        /*快取並且有網*/
        if (api.isCache() && AppUtil.isNetworkAvailable(RxRetrofitApp.getApplication())) {
             /*獲取快取資料*/
            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();
                }
            }
        }
    }複製程式碼

3.onCompleted()實現

 /**
     * 完成,隱藏ProgressDialog
     */
    @Override
    public void onCompleted() {
        dismissProgressDialog();
    }複製程式碼

4.onError(Throwable e)實現

onError(Throwable e)是對錯誤資訊的處理和快取讀取的處理,後續會講解,先忽略。

/**
     * 對錯誤進行統一處理
     * 隱藏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) {
                    /*獲取快取資料*/
                    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);
        }
    }

    /*錯誤統一處理*/
    private void errorDo(Throwable e) {
        Context context = mActivity.get();
        if (context == null) return;
        if (e instanceof SocketTimeoutException) {
            Toast.makeText(context, "網路中斷,請檢查您的網路狀態", Toast.LENGTH_SHORT).show();
        } else if (e instanceof ConnectException) {
            Toast.makeText(context, "網路中斷,請檢查您的網路狀態", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "錯誤" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
        if (mSubscriberOnNextListener.get() != null) {
            mSubscriberOnNextListener.get().onError(e);
        }
    }複製程式碼

5.onNext(T t)實現

   /**
     * 將onNext方法中的返回結果交給Activity或Fragment自己處理
     *
     * @param t 建立Subscriber時的泛型型別
     */
    @Override
    public void onNext(T t) {
        if (mSubscriberOnNextListener.get() != null) {
            mSubscriberOnNextListener.get().onNext(t);
        }
    }複製程式碼

主要是是將得到的結果,通過自定義的介面返回給view介面,其中的軟引用物件mSubscriberOnNextListener是自定義的介面回撥類HttpOnNextListener.


6.HttpOnNextListener封裝

現在只需關心onNext(T t)onError(Throwable e)介面即可,回撥的觸發點都是在上面的ProgressSubscriber中呼叫

/**
 * 成功回撥處理
 * Created by WZG on 2016/7/16.
 */
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(){

    }
}複製程式碼

失敗後的retry處理

這裡你可能會問,Retrofit有自帶的retry處理呀,的確Retrofit有自帶的retry處理,但是有很多的侷限,先看下使用

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.retryOnConnectionFailure(true);複製程式碼

使用起來還是很方便,只需要呼叫一個方法即可,但是它是不可控的,也就是沒有辦法設定retry時間次數,所以不太靈活,既然如此還不如自己封裝一下,因為用RxJava實現這個簡直小菜,無形中好像已經給RxJava打了廣告,中毒太深。

很簡單直接上程式碼:

 /**
 * retry條件
 * Created by WZG on 2016/10/17.
 */
public class RetryWhenNetworkException implements Func1<Observable<? extends Throwable>, Observable<?>> {
//    retry次數
    private int count = 3;
//    延遲
    private long delay = 3000;
//    疊加延遲
    private long increaseDelay = 3000;

    public RetryWhenNetworkException() {

    }

    public RetryWhenNetworkException(int count, long delay) {
        this.count = count;
        this.delay = delay;
    }

    public RetryWhenNetworkException(int count, long delay, long increaseDelay) {
        this.count = count;
        this.delay = delay;
        this.increaseDelay = increaseDelay;
    }

    @Override
    public Observable<?> call(Observable<? extends Throwable> observable) {
        return observable
                .zipWith(Observable.range(1, count + 1), new Func2<Throwable, Integer, Wrapper>() {
                    @Override
                    public Wrapper call(Throwable throwable, Integer integer) {
                        return new Wrapper(throwable, integer);
                    }
                }).flatMap(new Func1<Wrapper, Observable<?>>() {
                    @Override
                    public Observable<?> call(Wrapper wrapper) {
                        if ((wrapper.throwable instanceof ConnectException
                                || wrapper.throwable instanceof SocketTimeoutException
                                || wrapper.throwable instanceof TimeoutException)
                                && wrapper.index < count + 1) { //如果超出重試次數也丟擲錯誤,否則預設是會進入onCompleted
                            return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);

                        }
                        return Observable.error(wrapper.throwable);
                    }
                });
    }

    private class Wrapper {
        private int index;
        private Throwable throwable;

        public Wrapper(Throwable throwable, int index) {
            this.index = index;
            this.throwable = throwable;
        }
    }
}複製程式碼

使用

到這裡,我們第一步封裝已經完成了,下面講解下如何使用,已經看明白的各位看官,估計早就看明白了使用方式,無非是建立一個api物件繼承BaseApi初始介面資訊,然後呼叫HttpManager物件的doHttpDeal(BaseApi basePar)方法,最後靜靜的等待回撥類HttpOnNextListener<T>類返回的onNext(T t)成功資料或者onError(Throwable e)資料。

其實程式碼就是這樣:

api介面物件

/**
 * 測試資料
 * Created by WZG on 2016/7/16.
 */
public class SubjectPostApi extends BaseApi {
//    介面需要傳入的引數 可自定義不同型別
    private boolean all;
    /*任何你先要傳遞的引數*/
//    String xxxxx;

    /**
     * 預設初始化需要給定回撥和rx週期類
     * 可以額外設定請求設定載入框顯示,回撥等(可擴充套件)
     * @param listener
     * @param rxAppCompatActivity
     */
    public SubjectPostApi(HttpOnNextListener listener, RxAppCompatActivity rxAppCompatActivity) {
        super(listener,rxAppCompatActivity);
        setShowProgress(true);
        setCancel(true);
        setCache(true);
        setMothed("AppFiftyToneGraph/videoLink");
        setCookieNetWorkTime(60);
        setCookieNoNetWorkTime(24*60*60);
    }

    public boolean isAll() {
        return all;
    }

    public void setAll(boolean all) {
        this.all = all;
    }

    @Override
    public Observable getObservable(Retrofit retrofit) {
        HttpPostService service = retrofit.create(HttpPostService.class);
        return service.getAllVedioBys(isAll());
    }
}複製程式碼

請求回撥


    //    完美封裝簡化版
    private void simpleDo() {
        SubjectPostApi postEntity = new SubjectPostApi(simpleOnNextListener,this);
        postEntity.setAll(true);
        HttpManager manager = HttpManager.getInstance();
        manager.doHttpDeal(postEntity);
    }

    //   回撥一一對應
    HttpOnNextListener simpleOnNextListener = new HttpOnNextListener<List<SubjectResulte>>() {
        @Override
        public void onNext(List<SubjectResulte> subjects) {
            tvMsg.setText("網路返回:\n" + subjects.toString());
        }

        @Override
        public void onCacheNext(String cache) {
            /*快取回撥*/
            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() );
        }

        /*使用者主動呼叫,預設是不需要覆寫該方法*/
        @Override
        public void onError(Throwable e) {
            super.onError(e);
            tvMsg.setText("失敗:\n" + e.toString());
        }

        /*使用者主動呼叫,預設是不需要覆寫該方法*/
        @Override
        public void onCancel() {
            super.onCancel();
            tvMsg.setText("取消請求");
        }
    };複製程式碼

後續

到這裡,封裝功能中很多功能還沒涉及和講解,後續會陸續更新!
先給大家看看為師的完全體功能:

    1.Retrofit+Rxjava+okhttp基本使用方法
    2.統一處理請求資料格式
    3.統一的ProgressDialog和回撥Subscriber處理
    4.取消http請求
    5.預處理http請求
    6.返回資料的統一判斷
    7.失敗後的retry處理
    8.RxLifecycle管理生命週期,防止洩露
    9.檔案上傳下載(支援多檔案,斷點續傳)
    10.Cache資料持久化和資料庫(greenDao)兩種快取機制
    11.異常統一處理複製程式碼

來個圖壓壓驚:

RxRetrofit-終極封裝-深入淺出&網路請求

迫不及待的小夥伴可以看這裡:

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

但是其中有些後續優化迭代未及時更新,別生氣,我馬上補!


原始碼:

RxRetrofit-終極封裝-深入淺出&網路請求-GitHub

其實我還有一個兄弟版本-傳送門

我不會告訴你其實我還有個更加簡單的版本


建議

如果你對這套封裝有任何的問題和建議歡迎加入QQ群告訴我


這篇文章參加掘金技術徵文:gold.xitu.io/post/58522d…

相關文章