關於Retrofit的逐漸認識(二)

上篇文章 我們瞭解到Retrofit+RxJava  和一個簡單的將訂閱者Subscriber傳入Activity的封裝,而這一篇,我們從相同格式的Http請求數據如何封裝談起


相同格式的Http請求數據該如何封裝

如果我們的Http返回的數據是統一的格式,例如:
{
    "resultCode": 0,
     "resultMessage" : "成功",
      "data" : {}
}
resultCode和resultMessage的內容相對比較穩定,而data的內容變化多端,72變都不一定夠變的,有可能是個User對象,也有可能是個訂單對象,還有可能是個訂單列表。 按照我們之前的用法,使用Gson轉型需要我們在創建subscriber對象是指定返回值類型,如果我們對不同的返回值進行封裝的話,那可能就要有上百個Entity了,  如何封裝?

創建一個HttpResult的類, 相當於一個包裝類, 將結果包裝了起來,但是在使用的時候要給出一個明確的泛型
public class HttpResult<T> {
    private int resultCode;
    private String resultMessage;

    private T data;
}
如果data是一個User對象的話,那麼在定義Service方法的返回值就可以寫爲:
Observable<HttpResult<User>>

在上面的示例中,我也創建了一個HttpResult類,用來模仿這個形式,將其中的Subject dna單獨封裝了起來
public class HttpResult<T> {

    //用來模仿resultCode和resultMessage
    private int count;
    private int start;
    private int total;
    private String title;

    //用來模仿Data
    private T subjects;
}
這樣泛型就要寫爲:
Observable<HttpResult<List<Subject>>>

相同格式的Http請求數據統一進行預處理

既然我們有了相同的返回格式,那麼我們可能就需要在獲得數據之後進行一個統一的預處理。
當接受到了一個Http請求後,由於返回的結構統一爲:
{
 "resultCode": 0,
 "resultMessage": "成功",
 "data": {}
}

我們想要對resultCoderesultMessage先做一個判斷,因爲如果resultCode == 0代表success,那麼resultCode != 0data一般都是null

Activity或Fragment對resultCoderesultMessage基本沒有興趣,他們只對請求狀態data數據感興趣。

基於這種考慮,我們在resultCode != 0的時候,拋出個自定義的ApiException。這樣就會進入到subscriber的onError中,我們可以在onError中處理錯誤信息。

另外,請求成功時,需要將data數據轉換爲目標數據類型傳遞給subscriber,因爲,Activity和Fragment只想拿到和他們真正相關的數據。

使用Observable的map方法可以完成這一功能。

HttpMethods中創建一個內部類HttpResultFunc,代碼如下:   (這個可以實現兩兩類型裝換嗎)
/**
 * 用來統一處理Http的resultCode,並將HttpResult的Data部分剝離出來返回給subscriber
 *
 * @param <T> Subscriber真正需要的數據類型,也就是Data部分的數據類型
 */
private class HttpResultFunc<T> implements Func1<HttpResult<T>, T>{

    @Override
    public T call(HttpResult<T> httpResult) {
        if (httpResult.getResultCode() != 0) {
            throw new ApiException(httpResult.getResultCode());
        }
        return httpResult.getData();
    }
}

然後我們的getMovice方法該改改爲:
public void getTopMovie(Subscriber<List<Subject>> subscriber, int start, int count){

    movieService.getTopMovie(start, count)
            .map(new HttpResultFunc<List<Subject>>())
            .subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(subscriber);
}
由於HttpResult中的泛型T就是我們希望傳遞給subscriber的數據類型,而數據可以通過httpResult的getData方法獲得,這樣我們就處理了泛型問題,錯誤處理問題,還有將請求數據部分剝離出來給subscriber


這樣我們只需要關注Data數據的類型,而不必在關心整個過程了。

需要注意一點,就是在定義Service的時候,泛型是
HttpResult<User>
//or
HttpResult<List<Subject>>
而在定義Subscriber的時候泛型是 java User //or List<Subject>


不然你會得到一個轉型錯誤。


如何取消一個Http請求

用了RxJava之後,返回值是一個Observable,不能想call一樣cancel掉,  那麼如何取消Http請求
在查看RxJavaCallAdapterFactory這個類的源碼中, 我們看到Subscription,總結起來就是說,我們在Activity或者Fragment中創建subscriber對象,想要取消請求的時候調用subscriber的unsubscribe方法就可以了。

爲什麼會提到Observer?

Observer本身是一個接口,他的特性是不管你怎麼用,都不會解綁,爲什麼呢?因爲他沒有解綁的方法。所以就達到了複用的效果,一開始我一直美滋滋的用Observer。事實上,如果你用的是Observer,在調用Observable對象的subscribe方法的時候,會自動的將Observer對象轉換成Subscriber對象
public final Subscription subscribe(final Observer<? super T> observer) {
    if (observer instanceof Subscriber) {
        return subscribe((Subscriber<? super T>)observer);
    }
    return subscribe(new Subscriber<T>() {

        @Override
        public void onCompleted() {
            observer.onCompleted();
        }

        @Override
        public void onError(Throwable e) {
            observer.onError(e);
        }

        @Override
        public void onNext(T t) {
            observer.onNext(t);
        }

    });
}
但是後來發現了問題, ,由於Observer沒有unsubscribe方法,  故無法取消,  爲了後面,我們還是選擇使用Subscriber

一個需要ProgressDialog的Subscriber該有的樣子

我們希望有一個Subscriber在我們每次發送請求的時候能夠彈出一個ProgressDialog,然後在請求接受的時候讓這個ProgressDialog消失,同時在我們取消這個ProgressDialog的同時能夠取消當前的請求,而我們只需要處理裏面的數據就可以了。


我們先來創建一個類,就叫ProgressSubscriber,讓他繼承Subscriber。


Subscriber給我們提供了onStart、onNext、onError、onCompleted四個方法。


其中只有onNext方法返回了數據,那我們自然希望能夠在onNext裏面處理數據相關的邏輯。


onStart方法我們用來啓動一個ProgressDialog。 onError方法我們集中處理錯誤,同時也停止ProgressDialog onComplated方法裏面停止ProgressDialog


其中我們需要解決兩個問題

問題1 onNext的處理 問題2 cancel掉一個ProgressDialog的時候取消請求



我們先來處理問題1:

處理onNext

我們希望這裏能夠讓Activity或者Fragment自己處理onNext之後的邏輯,很自然的我們想到了用接口。問題還是泛型的問題,這裏面我們必須指定明確的類型。所以接口還是需要泛型。


我們先來定義一個接口,命名SubscriberOnNextListener
public interface SubscriberOnNextListener<T> {
    void onNext(T t);
}

代碼很簡單。再來看一下ProgressSubscriber現在的代碼
public class ProgressSubscriber<T> extends Subscriber<T> {

    private SubscriberOnNextListener mSubscriberOnNextListener;
    private Context context;

    public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
        this.mSubscriberOnNextListener = mSubscriberOnNextListener;
        this.context = context;
    }

    @Override
    public void onStart() {
    }

    @Override
    public void onCompleted() {
        Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onNext(T t) {
        mSubscriberOnNextListener.onNext(t);
    }
}

MainActivity使用是這樣的:

先來定義一個SubscriberOnNextListener對象,可以在onCreate裏面創建這個對象

private SubscriberOnNextListener getTopMovieOnNext;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    getTopMovieOnNext = new SubscriberOnNextListener<List<Subject>>() {
        @Override
        public void onNext(List<Subject> subjects) {
            resultTV.setText(subjects.toString());
        }
    };
}

getMovie方法這麼寫:

private void getMovie(){
 HttpMethods.getInstance().getTopMovie(
  new ProgressSubscriber(getTopMovieOnNext, MainActivity.this), 
  0, 10);
}
這樣Activity或Fragment就只需要關注拿到結果之後的邏輯了,其他的完全不用操心。


處理問題2 

處理ProgressDialog

我們希望當cancel掉ProgressDialog的時候,能夠取消訂閱,也就取消了當前的Http請求。 所以我們先來創建個接口來處理這件事情。
public interface ProgressCancelListener {
    void onCancelProgress();
}
然後我們用ProgressSubscriber來實現這個接口,這樣ProgressSubscriber就有了一個onCancelProgress方法,在這裏面取消訂閱。
@Override
public void onCancelProgress() {
    if (!this.isUnsubscribed()) {
        this.unsubscribe();
    }
}

然後我用了一個Handler來封裝了ProgressDialog。
public class ProgressDialogHandler extends Handler {

    public static final int SHOW_PROGRESS_DIALOG = 1;
    public static final int DISMISS_PROGRESS_DIALOG = 2;

    private ProgressDialog pd;

    private Context context;
    private boolean cancelable;
    private ProgressCancelListener mProgressCancelListener;

    public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener,
                                 boolean cancelable) {
        super();
        this.context = context;
        this.mProgressCancelListener = mProgressCancelListener;
        this.cancelable = cancelable;
    }

    private void initProgressDialog(){
        if (pd == null) {
            pd = new ProgressDialog(context);

            pd.setCancelable(cancelable);

            if (cancelable) {
                pd.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialogInterface) {
                        mProgressCancelListener.onCancelProgress();
                    }
                });
            }

            if (!pd.isShowing()) {
                pd.show();
            }
        }
    }

    private void dismissProgressDialog(){
        if (pd != null) {
            pd.dismiss();
            pd = null;
        }
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SHOW_PROGRESS_DIALOG:
                initProgressDialog();
                break;
            case DISMISS_PROGRESS_DIALOG:
                dismissProgressDialog();
                break;
        }
    }
}

Handler接收兩個消息來控制顯示Dialog還是關閉Dialog。 創建Handler的時候我們需要傳入ProgressCancelListener的對象實例。

最後貼出ProgressSubscriber的完整代碼:

public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{

    private SubscriberOnNextListener mSubscriberOnNextListener;
    private ProgressDialogHandler mProgressDialogHandler;

    private Context context;

    public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context) {
        this.mSubscriberOnNextListener = mSubscriberOnNextListener;
        this.context = context;
        mProgressDialogHandler = new ProgressDialogHandler(context, this, true);
    }

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

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

    @Override
    public void onStart() {
        showProgressDialog();
    }

    @Override
    public void onCompleted() {
        dismissProgressDialog();
        Toast.makeText(context, "Get Top Movie Completed", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onError(Throwable e) {
        dismissProgressDialog();
        Toast.makeText(context, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onNext(T t) {
        mSubscriberOnNextListener.onNext(t);
    }

    @Override
    public void onCancelProgress() {
        if (!this.isUnsubscribed()) {
            this.unsubscribe();
        }
    }
}

整個封裝完成, 如果你覺得寫更改線程的代碼覺得也很煩的話,可以把訂閱這部分也封裝起來:

public void getTopMovie(Subscriber<List<Subject>> subscriber, int start, int count){
  //原來的樣子
// movieService.getTopMovie(start, count)
// .map(new HttpResultFunc<List<Subject>>())
// .subscribeOn(Schedulers.io())
// .unsubscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(subscriber);


 //修改之後的樣子
    Observable observable = movieService.getTopMovie(start, count)
            .map(new HttpResultFunc<List<Subject>>());


    toSubscribe(observable, subscriber);
}


//添加線程管理並訂閱
private void toSubscribe(Observable o, Subscriber s){
     o.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(s);
}












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章