一步一步帶你認識MVP+Retrofit+Rxjava並封裝(二)

24K純帥豆發表於2017-10-16

一步一步帶你認識MVP+Retrofit+Rxjava並封裝(一)

一步一步帶你認識MVP+Retrofit+Rxjava並封裝(二)

序言:繼續上週我們們沒講完的,上週我們們一起擼了一把MVP,今天跟著LZ繼續擼Retrofit+RxJava,這倆算得上當下最流行的網路框架了,資料說話,有空的話你們也去github上搜尋一波:

我們可以看出來,單論star數的話,Retrofit和OkHttp是巨頭般的存在,網上關於retrofit的文章早已數不勝數了,這裡我也不詳細介紹了,下面就直接進入主題。

在開始之前,先介紹大家去學習一下RxJava,這個是真的灰常有用的一個庫,Rx系列的都很不錯,這裡有兩個版本,他們之間的方法稍微改變了一下,其他都差不多:

給初學者的RxJava2.0教程系列

給Android開發者的RxJava1.0詳解

如果你上週沒跟著LZ一起擼的話,那麼請移步:
一步一步帶你認識MVP+Retrofit+Rxjava並封裝(一)

1、導包:

//網路請求 retrofit+okhttp+gson
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'複製程式碼

2、封裝:

我們現來看一下,一個完整的Retrofit+Rxjva的請求:

OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.readTimeout(DEFAULT_TIME, TimeUnit.SECONDS);
builder.connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS);
//設定攔截器
builder.addInterceptor(new BasicParamsInterceptor.Builder().addParamsMap(getCommonMap()).build());
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
OkHttpClient okHttpClient = builder.build();
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(CustomGsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();
ApiService api=retrofit.create(ApiService.class);

api.login(username,password)
   .subscribeOn(Schedulers.io())               //在IO執行緒進行網路請求
   .observeOn(AndroidSchedulers.mainThread())  //回到主執行緒去處理請求結果
   .subscribe(new Observer<LoginResponse>() {
       @Override
       public void onSubscribe(Disposable d) {
               //為請求提供一個取消的手段
       }

       @Override
       public void onNext(LoginResponse value) {
               //請求成功
       }

       @Override
       public void onError(Throwable e) {
           //請求出錯
       }

       @Override
       public void onComplete() {
           //請求完成
       }
   });複製程式碼

考慮到每次請求介面的時候都需要去例項化一個Retrofit物件,而且每次都需要用RxJava來進行執行緒的切換,因此我就想到把它們都寫到一個基類裡面去。

 public abstract class BaseRetrofit {

    protected Retrofit mRetrofit;
    private static final int DEFAULT_TIME = 10;    //預設超時時間
    private final long RETRY_TIMES = 1;   //重訂閱次數
    public BaseRetrofit() {
        //建立okHttpClient
        if (null == mRetrofit) {
            OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
            builder.readTimeout(DEFAULT_TIME, TimeUnit.SECONDS);
            builder.connectTimeout(DEFAULT_TIME, TimeUnit.SECONDS);
            //設定攔截器
            builder.addInterceptor(new BasicParamsInterceptor.Builder().addParamsMap(getCommonMap()).build());
            builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
            OkHttpClient okHttpClient = builder.build();
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(HttpServletAddress.getInstance().getServletAddress())
                    .client(okHttpClient)
                    .addConverterFactory(CustomGsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();
        }
    }
 }複製程式碼

然後結合我們上週講的MVP,讓BaseModel繼承它,然後呼叫方法進行請求,上週我們還沒有細節講它是怎麼樣進行網路請求的,回到剛才那個完整的請求的例子,可以看到,這裡發起請求需要兩個東西,一個Observer,另一個是api.login()的返回值Observable,這就是大佬們口中說的觀察者和被觀察者,他們之間有一個很微妙的關係,叫訂閱;被觀察者負責網路請求,觀察者負責觀察網路請求的回撥,每發生一次介面請求,都會有訂閱發生,所以在這裡我把訂閱公共的邏輯放到了BaseRetrofit中:

 protected <T> void toSubscribe(Observable<T> observable, Observer<T> observer) {
    observable.subscribeOn(Schedulers.io())    // 指定subscribe()發生在IO執行緒
            .observeOn(AndroidSchedulers.mainThread())  // 指定Subscriber的回撥發生在io執行緒
            .timeout(DEFAULT_TIME, TimeUnit.SECONDS)    //重連間隔時間
            .retry(RETRY_TIMES)
//          .repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {
//                @Override
//                public ObservableSource<?> apply(@NonNull Observable<Object> objectObservable) throws Exception {
//                    return null;
//                }
//           })
            .subscribe(observer);   //訂閱
}複製程式碼

這樣每次我們組裝好ObservableObserver之後就呼叫這個方法進行訂閱就好了。這裡我有一個困惑,已經很久了,希望知道的讀者能幫忙解惑,重寫retryWhen的時候,如何根據錯誤型別進行重試。講到這裡可能有人就要問了,LZ你不還是沒有講是怎麼進行網路請求的嗎?大兄弟別急,我這就告訴你,它是通過自定義介面的形式來進行網路請求的,好吧,說了好像也白說,換個場景你自個去深入瞭解去吧:
Retrofit網路請求框架的基本使用

好了,接著我們下面的封裝:

被觀察者已經當作介面被我們處理掉了,那麼下面我們重點關注觀察者;很久之前我老大跟我講網路請求封裝這一塊,他當時說我們只關注請求成功的資料,其他的不需要特別關注;首先,我們得有一套統一的回撥樣式,如下:

{
    "status":1,
    "data":T
    "msg":"success"
}複製程式碼

由於我們這邊都把返回的json資料都轉成BaseResponse<T>格式了,如果你們回撥的資料格式不統一的話,那就去找後端撕逼去吧;然後我們只需要重寫Observer就行了,Observer介面中有四個方法,上面例子中我們簡單介紹了一下,它們的執行順序分別是onSubscribe——>onNext——>onComplete(onError),這裡需要簡單提一下,onCompleteonError方法二者不會同時都執行,具體來看一下LZ封裝的:

public abstract class BaseObserver<T> implements Observer<T> {

    private static final String TAG = "BaseObserver";

    protected abstract void onBaseError(Throwable t);

    protected abstract void onBaseNext(T data);

    protected abstract boolean isNeedProgressDialog();

    protected abstract String getTitleMsg();

    private ProgressDialogHandler mProgressDialogHandler;
    private BaseImpl mBaseImpl;

    public BaseObserver(BaseImpl baseImpl) {
        mBaseImpl = baseImpl;
        if (null != mBaseImpl) {
            if (null == mProgressDialogHandler) {
                mProgressDialogHandler = new ProgressDialogHandler(baseImpl.getContext(), true);
            }
        }
    }

    private void showProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG, getTitleMsg()).sendToTarget();
        }
    }

    private void dismissProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
            mProgressDialogHandler = null;
        }
    }

    @Override
    public void onSubscribe(Disposable d) {
        //顯示進度條
        if (isNeedProgressDialog()) {
            showProgressDialog();
        }
        if (null != mBaseImpl) {
            if (null != d) {
                mBaseImpl.addDisposable(d);
            }
        }
    }

    @Override
    public void onNext(T value) {
        //成功
        Log.d(TAG, "http is onNext");
        if (null != value) {
            onBaseNext(value);
        }
    }

    @Override
    public void onError(Throwable e) {
        //關閉進度條
        Log.e(TAG, "http is onError");
        if (isNeedProgressDialog()) {
            dismissProgressDialog();
        }
        onBaseError(e);
    }

    @Override
    public void onComplete() {
        //關閉進度條
        if (isNeedProgressDialog()) {
            dismissProgressDialog();
        }
    }
}複製程式碼

這裡考慮到有些介面需要進度框,所以我把這一部分也整合到觀察者裡面,這裡根據外面呼叫的地方有沒有設定Title來判斷是否顯示進度框,然後再進行相應的回撥,進度框使用的是系統的ProgressDialog,當然了,你也可以自定義一個進度框樣式,詳細見demo。前面我們說到我們只關心成功的資料,失敗的資料我們需要在內部處理掉,即再封裝一層,吃掉onBaseError

public abstract class CygBaseObserver<T> extends BaseObserver<T> {

    private static final String TAG = "CygBaseObserver";

    private boolean isNeedProgress;
    private String titleMsg;

    public CygBaseObserver() {
        this(null, null);
    }

    public CygBaseObserver(BaseImpl base) {
        this(base, null);
    }

    public CygBaseObserver(BaseImpl base, String titleMsg) {
        super(base);
        this.titleMsg = titleMsg;
        if (TextUtils.isEmpty(titleMsg)) {
            this.isNeedProgress = false;
        } else {
            this.isNeedProgress = true;
        }
    }

    @Override
    protected boolean isNeedProgressDialog() {
        return isNeedProgress;
    }

    @Override
    protected String getTitleMsg() {
        return titleMsg;
    }

    @Override
    protected void onBaseError(Throwable t) {
        StringBuffer sb = new StringBuffer();
        sb.append("請求失敗:");
        if (t instanceof NetworkErrorException || t instanceof UnknownHostException || t instanceof ConnectException) {
            sb.append("網路異常");
        } else if (t instanceof SocketTimeoutException || t instanceof InterruptedIOException || t instanceof TimeoutException) {
            sb.append("請求超時");
        } else if (t instanceof JsonSyntaxException) {
            sb.append("請求不合法");
        } else if (t instanceof JsonParseException
                || t instanceof JSONException
                || t instanceof ParseException) {   //  解析錯誤
            sb.append("解析錯誤");
        } else if (t instanceof ApiException) {
            if (((ApiException) t).isTokenExpried()) {
                sb.append("Token出錯");
            }
        } else {
            FRToast.showToastSafe(t.getMessage());
            return;
        }
        Log.e(TAG, "onBaseError: " + sb.toString());
        FRToast.showToastSafe(sb.toString());
    }
}複製程式碼

最開始的例子當中有一個Disposable的概念,這個是用來切斷觀察者與被觀察者之間的關係的,每次請求都會產生一個響應的Disposable,所以這裡我用了一個介面BaseImpl的形式來回收它,在產生的地方收集它,在BaseActivity的onDestroy中來回收它,詳細的請參見demo;好了,到這裡我們的封裝就完成了90%了,讓我們回到上一次部落格當中,組裝Observable的時候我們還進行了一個map操作:


這裡map就是進行一箇中間的操作,這個操作叫做變換,我們來看一下HttpFunction的實現是怎樣的:

public class HttpFunction<T> implements Function<BaseResponse<T>, T> {

    @Override
    public T apply(BaseResponse<T> response) throws Exception {
        if (!response.isRequestSuccess()) {
            throw new ApiException(response.getStatus(), String.valueOf(response.getMsg()));
        }
        return response.getData();
    }
}複製程式碼

相信看完方法的實現大家應該知道這個是幹什麼用的了,沒錯,這個方法就是將BaseResponse<T>轉換成T,因為我們只關注成功的資料,而且只關注data裡面的資料,由於返回的資料是BaseResponse<T>,而我們需要關注的資料是T,所以在這裡需要轉換一下,然後判斷請求是否成功就行了。

#####最後的最後,上面我提到一個問題,就是如何重寫retryWhen方法,根據錯誤型別來對請求進行重試操作,這裡給出最終的程式碼(這裡LZ是想在網路出錯或者連線超時的時候進行重試):

.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {

                private int mRetryCount;

                @Override
                public ObservableSource<?> apply(@NonNull Observable<Throwable> throwableObservable) throws Exception {
                    return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                        @Override
                        public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                            if ((throwable instanceof NetworkErrorException
                                    || throwable instanceof ConnectException
                                    || throwable instanceof SocketTimeoutException
                                    || throwable instanceof TimeoutException) && mRetryCount < 3) {
                                mRetryCount++;
                                return Observable.timer(2000, TimeUnit.MILLISECONDS);
                            }
                            return Observable.error(throwable);
                        }
                    });
                }
            })複製程式碼

好了,到這裡基本的網路請求封裝就完成了,如果你有更好的方法,請私信我一起交流,同時感謝以上引用到部落格的博主。最後的最後,放出最後的程式碼,歡迎starfork

MVP 主工程程式碼

MVP module工程程式碼

公眾號:Android技術經驗分享
公眾號:Android技術經驗分享

相關文章