Android 實(shí)現(xiàn)一個(gè) MVP 基礎(chǔ)框架

Kotlin 版的可參考:https://github.com/SheHuan/WanAndroid

這幾年 MVP 在 Android 開發(fā)中已經(jīng)開始被廣泛使用,逐漸成為一種主流的設(shè)計(jì)思想。在 MVP 出現(xiàn)之前,我們使用最多的可能就是 MVC 了,那么我們?yōu)槭裁匆褂?MVP,它解決了 MVC 使用中的那些痛點(diǎn)呢,那我們先從 MVC 說起。

一、淺談 MVC

MVC 的全稱是 Model-View-Controller,這三部分在 Android 中可以按照如下的層次劃分:

  • Model(數(shù)據(jù)模型層)
    主要負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)等其它 I/O 操作
  • View(視圖層)
    主要包括 XML 布局文件(但功能較弱)
  • Controller(控制器層)
    一般就是 Activity 或者 Fragment,負(fù)責(zé) MVC 整個(gè)流程的調(diào)度、協(xié)作。但是由于 XML 的視圖處理能力較弱,需要 Activity 或者 Fragment 承擔(dān)部分視圖層工作。

在 Android 中可以用網(wǎng)絡(luò)請(qǐng)求的例子來梳理一下 MVC 的流程,例如,在 Activity(Controller)中通過一個(gè) Button 點(diǎn)擊事件調(diào)用 Model 層代碼發(fā)起網(wǎng)絡(luò)請(qǐng)求,當(dāng) Model 層網(wǎng)絡(luò)請(qǐng)求成功響應(yīng)后,在對(duì)應(yīng)的回調(diào)函數(shù)中去更新 View 層的一個(gè) RecyclerView。按照事件的流向,例子中 MVC 三部分的關(guān)系如下:

MVC

你可能還見過不同的 MVC 關(guān)系圖,其實(shí)都是對(duì)的,因?yàn)椴煌?MVC 實(shí)現(xiàn)可能會(huì)有不同的事件流向,選擇適合自己的就行。

上圖看起來層次結(jié)構(gòu)很清晰,但還是存在一些問題的,雖然我們可以把網(wǎng)絡(luò)請(qǐng)求的 Model 層解耦成一個(gè)單獨(dú)的模塊,但從網(wǎng)絡(luò)請(qǐng)求的發(fā)起到響應(yīng)結(jié)果的處理都集中在了 Activity 中,View 層的功能基本被弱化的可以忽略了,基本由 Activity 承擔(dān)了,所以 Activity 同時(shí)承擔(dān)了 Controller 和 View 層的職責(zé),帶來了耦合的問題,隨著業(yè)務(wù)量的增加必然會(huì)導(dǎo)致 Activity 變的臃腫起來,增加維護(hù)成本。

那么面對(duì)這些問題 MVP 是如何應(yīng)對(duì)的呢,它和 MVC 有何異同呢......

二、更好的 MVP

MVP 的全稱是 Model-View-Presenter,它保留了 MVC 中的 View 層和 Model 層,用 Presenter 層替代了 Controller 層,在 Android 開發(fā)中三個(gè)層次的職責(zé)劃分如下:

  • Model(數(shù)據(jù)模型層)
    主要負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)等其它 I/O 操作,和 MVC 中的 Model 層類似
  • View(視圖層)
    主要是 Activity、Fragment、XML布局文件
  • Presenter(控制器層)
    自定義的 Presenter 類,類似于 MVC 中 Controller 的作用,是 MVP 中的核心,主要負(fù)責(zé) Model 層和 View 層的交互

在 MVP 中 Activity、Fragment、XML都?xì)w屬到了 View 層,即不存在 MVC 中 V、C層之間耦合的問題。在 MVC 中,Model 層功能代碼的調(diào)用是在 Activity、Fragment 中進(jìn)行的,但在 MVP 中這個(gè)職責(zé)將由 Presenter 去完成,然后將結(jié)果通過接口傳遞給 View 層,可以將 Model 層的實(shí)現(xiàn)、調(diào)用對(duì) View 層隱藏起來,這樣可以有效的降低 Activity、Fragment 的代碼量,避免代碼臃腫。

所以相比 MVC,MVP 的職責(zé)劃分更加清晰,能夠更好額解耦,但也會(huì)增加一些類文件。

下邊是一個(gè) MVP 的事件流向圖,可以看到,在 MVP 中我們約定 Model 層和 View 層是不能直接通信的,而是通過 Presenter 層完成交互。

MVP

三、簡(jiǎn)單上手

理論性的東西說了一堆,難免有些瞌睡,先看一個(gè)使用的例子。

首先說明一下,我們的基礎(chǔ)框架中用到了 RxJava2、Retrofit2

這里使用了 玩Android 的開放api接口進(jìn)行測(cè)試,測(cè)試demo的目錄結(jié)構(gòu)如下:

demo

首先看 WanAndroidApis接口,按照 Retrofit2 的要求,需要在里邊聲明網(wǎng)絡(luò)請(qǐng)求的接口:

public interface WanAndroidApis {
    String BASE_URL = Url.WAN_ANDROID_UTL;

    @GET("banner/json")
    Observable<BaseResponse<List<BannerBean>>> banner();

    @GET("friend/json")
    Observable<BaseResponse<List<FriendBean>>> friend();
}

bean 目錄對(duì)應(yīng)接口返回 JSON 對(duì)應(yīng)的類,就不多說了。

SampleContract是一個(gè)契約接口,這里約定了 Activity 需要實(shí)現(xiàn)的接口,以及接下來 Presenter 類中需要實(shí)現(xiàn)的業(yè)務(wù)方法:

public interface SampleContract {
    interface View extends BaseView {
        void onBannerSuccess(List<BannerBean> data);

        void onBannerError(ResponseException e);

        void onFriendSuccess(List<FriendBean> data);

        void onFriendError(ResponseException e);
    }

    interface Presenter {
        void getBannerData();

        void getFriendData();
    }
}

SamplePresenterImpl類對(duì)應(yīng) MVP 中的 P,繼承了我們基礎(chǔ)框架中封裝的BasePresenter類,同時(shí)實(shí)現(xiàn)了 SampleContract中的Presenter接口:

public class SamplePresenterImpl extends BasePresenter<SampleContract.View> implements SampleContract.Presenter {
    public SamplePresenterImpl(Context context, SampleContract.View view) {
        super(context, view);
    }

    @Override
    public void getBannerData() {
        RequestManager.getInstance().execute(this, RetrofitManager.getInstance().create(WanAndroidApis.class).banner(),
                new BaseObserver<List<BannerBean>>(context, true, true) {
                    @Override
                    protected void onSuccess(List<BannerBean> data) {
                        view.onBannerSuccess(data);
                    }

                    @Override
                    protected void onError(ResponseException e) {
                        view.onBannerError(e);
                    }
                });
    }

    @Override
    public void getFriendData() {
        RequestManager.getInstance().execute(this, RetrofitManager.getInstance().create(WanAndroidApis.class).friend(),
                new BaseObserver<List<FriendBean>>(true) {
                    @Override
                    protected void onSuccess(List<FriendBean> data) {
                        view.onFriendSuccess(data);
                    }

                    @Override
                    protected void onError(ResponseException e) {
                        view.onFriendError(e);
                    }
                });
    }
}

最后來看MainActivity,繼承了我們自己封裝的基類BaseMvpActivity,同時(shí)實(shí)現(xiàn)了SampleContract中的View接口,并完成對(duì)應(yīng) Presenter 類的初始化:

public class MainActivity extends BaseMvpActivity<SamplePresenterImpl> implements SampleContract.View {

    // 初始化 Presenter 類
    @Override
    protected SamplePresenterImpl initPresenter() {
        return new SamplePresenterImpl(context, this);
    }

    //  通過 Presenter 發(fā)起初始化網(wǎng)絡(luò)請(qǐng)求
    @Override
    protected void loadData() {
        presenter.getBannerData();
        presenter.getFriendData();
    }
    // 設(shè)置布局文件 id
    @Override
    protected int initLayoutResID() {
        return R.layout.activity_main;
    }
    // 數(shù)據(jù)初始化
    @Override
    protected void initData() {

    }
    // 控件初始化
    @Override
    protected void initView() {

    }

    @Override
    public void onBannerSuccess(List<BannerBean> data) {
        Log.e("banner", "success");
    }

    @Override
    public void onBannerError(ResponseException e) {
        Log.e("banner", "error");
    }

    @Override
    public void onFriendSuccess(List<FriendBean> data) {
        Log.e("friend", "success");
    }

    @Override
    public void onFriendError(ResponseException e) {
        Log.e("friend", "error");
    }
}

到這里一個(gè)基本的使用就完成了,在 Fragment 中的使用也是類似的。解耦效果顯而易見,職責(zé)劃分更加清晰。

其實(shí)核心的就是每個(gè)有 MVP 需求的 Activity 或 Fragment 需要定義一個(gè) Contract 契約接口與之對(duì)應(yīng),然后再實(shí)現(xiàn)一個(gè)具體的 Presenter 類。

更多實(shí)現(xiàn)細(xì)節(jié)可參考:https://github.com/SheHuan/EasyMvp

四、MVP 的封裝過程

既然要封裝,我們就需要盡可能的把通用的代碼抽離出來,將一些常見的問題、需求在基礎(chǔ)代碼中處理好。封裝后基礎(chǔ)框架目錄結(jié)構(gòu)如下:


easymvp

1、Model 層

首先看 Model 層的封裝,Model 層主要封裝了基于 Retrofit2 的網(wǎng)絡(luò)請(qǐng)求,即RetrofitManager類:

public class RetrofitManager {
    private OkHttpClient okHttpClient;
    private RetrofitManager() {
    }

    public static RetrofitManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final RetrofitManager INSTANCE = new RetrofitManager();
    }

    /**
     * 根據(jù)不同的ApiService接口創(chuàng)建ApiService對(duì)象
     */
    public <S> S create(Class<S> service) {
        Retrofit retrofit = new Retrofit.Builder()
                .client(getOkHttpClient(true)) // 設(shè)置請(qǐng)求超時(shí)、日志相關(guān)的攔截器
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(getBaseUrl(service))
                .build();
        return retrofit.create(service);
    }

    /**
     * 解析接口中的BASE_URL,解決BASE_URL不一致的問題,如果所有的BASE_URL都一致,則可不用該方法
     */
    private <S> String getBaseUrl(Class<S> service) {

        try {
            Field field = service.getField("BASE_URL");
            return (String) field.get(service);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
.......
}

Retrofit2 和 RxJava2 是一對(duì)好搭檔,可以方便的幫助我們完成網(wǎng)絡(luò)請(qǐng)求的線程切換以及相應(yīng)處理,所以RequestManager就是一個(gè)可以直接使用的網(wǎng)絡(luò)請(qǐng)求類:

public class RequestManager {
    private RequestManager() {
    }

    public static RequestManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final RequestManager INSTANCE = new RequestManager();
    }

    /**
     * 通用網(wǎng)絡(luò)請(qǐng)求方法
     */
    public <E> Disposable execute(BasePresenter presenter, Observable<BaseResponse<E>> observable, BaseObserver<E> observer) {
        observable
                .map(new ResponseConvert<E>())
                .onErrorResumeNext(new ExceptionConvert<E>())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
        presenter.addDisposable(observer.getDisposable());
        return observer.getDisposable();
    }

    /**
     * 通用耗時(shí)任務(wù)執(zhí)行方法
     */
    public <E> Disposable commonExecute(BasePresenter presenter, final ExecuteListener<E> listener, BaseObserver<E> observer) {
        ......
    }

    /**
     * 同時(shí)執(zhí)行兩個(gè)網(wǎng)絡(luò)請(qǐng)求,統(tǒng)一處理請(qǐng)求結(jié)果
     */
    public <E1, E2, E3> Disposable zipExecute(BasePresenter presenter, Observable<BaseResponse<E1>> observable1, Observable<BaseResponse<E2>> observable2, final ZipExecuteListener<E1, E2, E3> listener, BaseObserver<E3> observer) {
        ......
    }

    /**
     * 依次執(zhí)行兩個(gè)網(wǎng)絡(luò)請(qǐng)求
     */
    public <E1, E2> Disposable orderExecute(BasePresenter presenter, Observable<BaseResponse<E1>> observable1, final OrderExecuteListener<E1, E2> listener, BaseObserver<E2> observer) {
        ......
    }
}

BaseObserver類相當(dāng)于是網(wǎng)絡(luò)請(qǐng)求的觀察者類,可以用來監(jiān)聽網(wǎng)絡(luò)請(qǐng)求的開始、完成、發(fā)生錯(cuò)誤等:

public abstract class BaseObserver<E> implements Observer<E> {
    private WeakReference<Context> wrContext;
    private Disposable disposable;
    private BaseNiceDialog dialog;
    // 發(fā)生異常時(shí),是否顯示對(duì)應(yīng)的 Toast 提示
    private boolean showErrorTip;

    /**
     * @param context      由于loading通過DialogFragment實(shí)現(xiàn),無法使用Application Context,需要使用Activity Context
     * @param showLoading  是否顯示加載中l(wèi)oading
     * @param showErrorTip 發(fā)生異常時(shí),是否使用Toast提示
     */
    public BaseObserver(Context context, boolean showLoading, boolean showErrorTip) {
        wrContext = new WeakReference<>(context);
        this.showErrorTip = showErrorTip;
        if (showLoading) {
            initLoading();
        }
    }

    public BaseObserver(boolean showErrorTip) {
        this.showErrorTip = showErrorTip;
        wrContext = new WeakReference<>(App.getContext());
    }

    @Override
    public void onSubscribe(Disposable d) {
        disposable = d;
        showLoading();
    }

    @Override
    public void onNext(E data) {
        hideLoading();
        onSuccess(data);
    }

    @Override
    public void onError(Throwable e) {
        hideLoading();
        ResponseException responseException = (ResponseException) e;
        if (showErrorTip) {
            Toast.makeText(wrContext.get(), responseException.getErrorMessage(), Toast.LENGTH_SHORT).show();
        }
        onError(responseException);
    }

    @Override
    public void onComplete() {

    }

    public Disposable getDisposable() {
        return disposable;
    }

    protected abstract void onSuccess(E data);

    protected abstract void onError(ResponseException e);

    /**
     * 初始化loading
     */
    private void initLoading() {
        dialog = NiceDialog.init()
                .setLayoutId(R.layout.loading_layout)
                .setWidth(100)
                .setHeight(100)
                .setDimAmount(0);
    }

    /**
     * 顯示loading
     */
    private void showLoading() {
        if (dialog != null) {
            dialog.show(((BaseActivity) wrContext.get()).getSupportFragmentManager());
        }
    }

    /**
     * 取消loading
     */
    private void hideLoading() {
        if (dialog != null) {
            dialog.dismiss();
            dialog = null;
        }
    }
}

在這里邊我們可以按需顯示耗時(shí)任務(wù)過程中的 Loading,以及異常提示 Toast。

RequestManager類還用到了一個(gè)ResponseConvert類,將BaseResponse響應(yīng)根據(jù)接口約定的響應(yīng)碼進(jìn)一步處理

public class ResponseConvert<E> implements Function<BaseResponse<E>, E> {
    @Override
    public E apply(BaseResponse<E> baseResponse) {
        if (!"0".equals(baseResponse.getErrorCode())) {
            // 手動(dòng)拋出異常
            throw new ApiException(baseResponse.getErrorCode(), baseResponse.getErrorMsg());
        }
        return baseResponse.getData();
    }
}

由于測(cè)試代碼使用了 玩Android 的api,對(duì)應(yīng)的響應(yīng)數(shù)據(jù)bean可以這樣定義:

public class BaseResponse<T>{
    private String errorCode;
    private String errorMsg;
    private T data;
    ......
}

BaseResponse類的前兩個(gè)字段需要根據(jù)自己接口的數(shù)據(jù)格式進(jìn)行修改。

RequestManager類中還有一個(gè)ExceptionConvert類,當(dāng)網(wǎng)絡(luò)請(qǐng)求過程中遇到異常時(shí),不會(huì)直接拋出異常,而是進(jìn)一步轉(zhuǎn)發(fā)異常信息,方便統(tǒng)一處理:

public class ExceptionConvert<E> implements Function<Throwable, ObservableSource<? extends E>> {
    @Override
    public ObservableSource<? extends E> apply(Throwable throwable) throws Exception {
        return Observable.error(ExceptionHandler.handle(throwable));
    }
}

ExceptionHandler類會(huì)對(duì)異常信息進(jìn)行統(tǒng)一的轉(zhuǎn)換處理,如果需要定制部分異常提示信息則可以著手修改該類。
到此 Model 層的核心功能就實(shí)現(xiàn)了,也是我們封裝過程中最復(fù)雜的部分。

2、Presente 層

接下來看 Presenter 層的實(shí)現(xiàn),就是一個(gè)BasePresenter類:

public abstract class BasePresenter<V extends BaseView> {
    protected V view;
    protected Context context;
    private CompositeDisposable compositeDisposable;

    public BasePresenter(Context context, V view) {
        this.context = context;
        this.view = view;
        compositeDisposable = new CompositeDisposable();
    }

    /**
     * 保存RxJava綁定關(guān)系
     */
    public void addDisposable(Disposable disposable) {
        if (!compositeDisposable.isDisposed()) {
            compositeDisposable.add(disposable);
        }
    }

    /**
     * 取消單個(gè)RxJava綁定
     */
    public void removeDisposable(Disposable disposable) {
        if (!compositeDisposable.isDisposed()) {
            compositeDisposable.remove(disposable);
        }
    }

    /**
     * 當(dāng)Activity、Fragment destory時(shí),取消當(dāng)前Presenter的全部RxJava綁定,置空view
     */
    public void detach() {
        if (!compositeDisposable.isDisposed()) {
            compositeDisposable.clear();
        }
        view = null;
    }
}

核心的功能就是保存 RxJava 訂閱時(shí)返回的Disposable對(duì)象,這一點(diǎn)在RequestManagerexecute方法中已經(jīng)有所體現(xiàn)。還有就是取消 RxJava 訂閱關(guān)系,防止內(nèi)存泄漏、發(fā)生異常。

3、View 層

Presenter類是在 View 層,即基類 BaseMvpActivityBaseMvpFragment中完成初始化以及DisposableView接口對(duì)象的釋放操作:

public abstract class BaseMvpActivity<P extends BasePresenter> extends BaseActivity {
    protected P presenter;

    // 初始化Presenter
    protected abstract P initPresenter();

    // 默認(rèn)數(shù)據(jù)請(qǐng)求
    protected abstract void loadData();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = initPresenter();
        loadData();
    }

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

View 層的實(shí)現(xiàn)相對(duì)簡(jiǎn)單,BaseMvpFragment中我們實(shí)現(xiàn)了 Fragment 懶加載操作,在BaseActivity、BaseFragment中默認(rèn)初始化了 ButterKnife 方便控件的綁定。

大家應(yīng)該還見過其它結(jié)構(gòu)的 MVP 代碼,因?yàn)?MVP 更是一種設(shè)計(jì)思想,并沒有限制性的規(guī)定你的代碼必須要怎么寫,所以只要我們的思路正確,適合自己的封裝就是好的。

更多實(shí)現(xiàn)細(xì)節(jié)可參考:https://github.com/SheHuan/EasyMvp

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容