1. 程式人生 > >RxJava+Retrofit2+MVP實現網路請求

RxJava+Retrofit2+MVP實現網路請求

上一遍部落格介紹了RxJava+Retrofit2的使用。在前段時間,刷招聘簡歷的時候,發現有一部分的公司會要求MVP模式的理解和具體使用。在現在越來越複雜的業務,我們的Activity的負擔也是越來越大,因此接著這篇我結合MVP模式來介紹一下自己對RxJava+Retrofit2+MVP的使用。

MVP的理解

RxJava+Retrofit2+MVP三者結合使用

MVP的理解

因為我相信大家在開發App時,肯定會發現,Activity的負擔非常重,既要初始化控制元件,又要寫一些邏輯操作的展示等等,有時候很多Activity中的程式碼都充當了Controller和Model的角色,所以你會發現Activity違背單一職責原則,負擔過重。所以,就出現了這麼一種架構模式,叫MVP。
而當將架構改為MVP以後,Presenter的出現,將Actvity視為View層,Presenter負責完成View層與Model層的互動。現在是這樣的:

  • 檢視(View)對應於Activity,負責View的繪製以及與使用者互動
  • 模型(Model)依然是業務邏輯和實體模型
  • 主持人(Presenter)相當於協調者,是模型與檢視之間的橋樑,負責完成View於Model間的互動,將模型與檢視分離開來。

借下圖一用,View與Model並不直接互動,而是使用Presenter作為View與Model之間的橋樑。其中Presenter中同時持有 Viwe層以及Model層的Interface的引用,而View層持有Presenter層Interface的引用。當View層某個介面需要展示 某些資料的時候,首先會呼叫Presenter層的某個介面,然後Presenter層會呼叫Model層請求資料,當Model層資料載入成功之後會調 用Presenter層的回撥方法通知Presenter層資料載入完畢,最後Presenter層再呼叫View層的介面將載入後的資料展示給使用者。這 就是MVP模式的整個核心過程。
這裡寫圖片描述


這樣分層的好處就是大大減少了Model與View層之間的耦合度。一方面可以使得View層和Model層單獨開發與測試,互不依賴。另一方面 Model層可以封裝複用,可以極大的減少程式碼量。當然,MVP還有其他的一些優點,這裡不再贅述。
但是你可以發現這裡超級多累簡直就是類數量爆炸,程式碼複雜度和學習成本高,還有在某些場景下presenter的複用會產生介面冗餘。所以MVP模式是不是適合你使用?在使用之前需要思考一下是否有必要對自己的專案動刀?

RxJava+Retrofit2+MVP三者結合使用

首先看看我們用到的庫

dependencies {
    compile fileTree(dir
: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.1.0' compile 'com.google.code.gson:gson:2.7' compile 'com.jakewharton:butterknife:7.0.1' compile 'com.squareup.okhttp3:okhttp:3.3.0' compile 'com.squareup.okhttp3:logging-interceptor:3.3.0' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' }

再看看包目錄結構,相信有部分的同學曾糾結應該如何怎麼佈置自己的專案目錄結構,可以參考一下我的方式;
這裡寫圖片描述

View:使用者在登入介面互動觸發的事件,我們需要處理到的方法。這裡需要我們想象一下在我們的Activity中一共會出現的互動有哪些。

public interface IUserLoginView {
    void loginSuccess();

    void loginFail(int toast, String reason);

    void showLoading();

    void hideLoading();

    void saveCommonUserInfo(UserInfo info);
}

負責登入的Activity

public class LoginActivity extends MvpActivity<UserLoginPresenter> implements IUserLoginView {
    private Context mContext;
    @Bind(R.id.editText_name)
    EditText mEtGwNumber;
    @Bind(R.id.editText_pwd)
    EditText mEtPassword;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mContext = this;
    }

    @Override
    protected UserLoginPresenter createPresenter() {
        return new UserLoginPresenter(this);
    }

    @OnClick(R.id.button_login)
    public void onClickLogin() {
        Map<String, String> maps = new HashMap<>();
        maps.put("name", mEtGwNumber.getText().toString());
        maps.put("pwd", mEtPassword.getText().toString());
        mvpPresenter.login(maps);
    }

    @Override
    public void loginSuccess() {
        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
        startActivity(intent);
    }

    @Override
    public void loginFail(int toast, String reason) {
        if (reason.isEmpty()) {
            Toast.makeText(LoginActivity.this, getString(toast), Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(LoginActivity.this, reason, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void showLoading() {
        DialogUtils.getInstance().popRemindDialog(mContext, "正在登入");
    }

    @Override
    public void hideLoading() {
        DialogUtils.getInstance().disMissRemind();
    }

    @Override
    public void saveCommonUserInfo(UserInfo info) {
        //這裡將登入介面返回的資料,根據實際情況儲存在SP或者SQLite
    }
}

這裡會繼承一個MvpActivity,管理Presenter對Activity的生命週期,防止Presenter持有Activity物件可能導致的記憶體洩漏問題。

public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity {
    protected P mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mvpPresenter = createPresenter();
        super.onCreate(savedInstanceState);
    }

    protected abstract P createPresenter();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mvpPresenter != null) {
            mvpPresenter.detachView();
        }
    }
}

使用者點選登入按鈕,實現IUserLoginModel介面,傳入一個Map集合物件,返回Subscription型別

public interface IUserLoginModel {
    Subscription login(Map<String, String> maps, OnLoginListener listener);
}

對IUserLoginModel介面進行結果監聽

public interface OnLoginListener {
    void loginSuccess(UserInfo userInfo);

    void loginFailed(int toast, String reason);

    void requestCompleted();
}

通過UserLoginModel去實現IUserLoginModel介面,結合RxJava+Retrofit2實現。這裡的具體內容可以引數我上篇部落格。

public class UserLoginModel implements IUserLoginModel {
    @Override
    public Subscription login(Map<String, String> maps, final OnLoginListener listener) {
        Observable<JSONObject> observable = RetrofitManage.getInstance().getHttpServiceConnection().login(maps);
        return observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io()) //在io執行緒中處理網路請求
                .observeOn(AndroidSchedulers.mainThread()).subscribe(new SubscriberCallBack<>(new ApiCallback<JSONObject>() {
                    @Override
                    public void onSuccess(JSONObject model) {
                        UserInfo userInfo = JsonHandleAdapter.getUserLoginInfo(model.toString());
                        listener.loginSuccess(userInfo);
                    }

                    @Override
                    public void onFailure(int msg, String reason) {
                        listener.loginFailed(msg, reason);
                    }

                    @Override
                    public void onCompleted() {
                        listener.requestCompleted();
                    }
                }));
    }
}

到View和Model的中轉站Presenter,這裡將Model返回的結果進行判斷處理。

首先Presenter的基類BasePresenter

public class BasePresenter<V> implements Presenter<V> {
    public V mvpView;
    private CompositeSubscription mCompositeSubscription;

    @Override
    public void attachView(V view) {
        this.mvpView = view;
    }

    @Override
    public void detachView() {
        this.mvpView = null;
        onUnSubscribe();
    }

    //RxJava取消註冊,以避免記憶體洩露
    public void onUnSubscribe() {
        if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
            mCompositeSubscription.unsubscribe();
        }
    }


    public void addSubscription(Subscription subscription) {
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(subscription);
    }
}

泛型Presenter:

public interface Presenter<V> {
    void attachView(V view);

    void detachView();
}

最終我們的UserLoginPresenter登入Presenter,我們這裡登入Presenter,簡化了不少,但是具體思路是這樣。

public class UserLoginPresenter extends BasePresenter<IUserLoginView> {
    private IUserLoginModel iUserLoginModel;

    public UserLoginPresenter(IUserLoginView view) {
        attachView(view);
        iUserLoginModel = new UserLoginModel();
    }

    public void login(Map<String, String> maps) {
        mvpView.showLoading();
        addSubscription(iUserLoginModel.login(maps, new OnLoginListener() {
            @Override
            public void loginSuccess(UserInfo userInfo) {
                mvpView.loginSuccess();
                if (null != userInfo)
                    mvpView.saveCommonUserInfo(userInfo);
            }

            @Override
            public void loginFailed(int toast, String reason) {
                mvpView.loginFail(toast, reason);
            }

            @Override
            public void requestCompleted() {
                mvpView.hideLoading();
            }
        }));
    }
}

注意上述程式碼,我們的Presenter完成View和Model的互動。首先我們在View接收到使用者的操作,我們通過實現Presenter的登入方法,在Model的登入方法中完成耗時操作網路請求,請求後的結果回撥給Presenter,然後Presenter在呼叫View的物件,執行View對應的方法。

總結:
這裡小小的登入介面,就需要到很多實現介面類,所以在我們的實際情況中,到底要不要全部採用MVP模式呢?需要大傢俱體情況具體分析。不要盲目認為MVP模式好厲害,好整潔就使用,畢竟我們需要保證我們的程式正常執行,程式碼如期迭代,其他的同學看你的程式碼不會暈掉。
另外我的封裝方式不一定適用你,但是這個方式是在我線上的專案中已經實行了。不過我感覺還是需要繼續迭代、繼續優化、繼續學習。謝謝大家!

原始碼點選下載:

這裡寫圖片描述