Android应用模板之MVP, MVVM模式

应用模板代码地址:https://github.com/thfhongfeng/AndroidAppTemplate

其实所谓架构模式,无非是一个通过接口化实现代码解耦分层的过程。而接口的实质就是一个输入输出的规范。这个规范成为各个分层代码的沟通桥梁,使得各个分层只关心自己要做的事,即告诉我你需要我提供什么(输出),并且给我相关的证明(输入),至于你要用来做什么,我不关心。

MVP模式

先上通用图

MVP

View发送指令给Presenter,Presenter获取指令后,调用响应的Model进行业务逻辑处理,然后返回数据给Presenter,Presenter根据Model返回的数据再来调用相应的View。

View:我的服务对象是用户,用户告诉我要给它提供哪些显示和响应服务,我给出相应的服务;
Presenter:我的服务对象是View,View告诉我哪些UI界面需要显示和响应,我根据它提供的内容适时的给它提供这些View所需的数据内容;
Model:我的服务对象是Presenter,Presenter告诉我需要哪些业务数据,我根据它提供的内容适时给它提供业务数据。


MVP基础代码目录结构

上图是mvp架构的各个基类,业务模块通过继承这些基类来实现mvp架构。


IContract接口集:规范了Ui(View层)接口和Presenter(Presenter层)接口,这里没有做model层的接口化,实际上这里的接口定义并不是说一定每层都必须要,而应该是根据自己的项目来灵活处理。所谓的架构其实只是给了一个分层的规范,好比一个公司的员工组织架构:管理做规划,技术人员实现产品,客户经理推广产品,而助理就负责这些岗位的沟通工作。但有时候公司规模小或者公司性质原因,可能就不需要助理而是采用直接沟通的方式。所以这里接口化的处理是根据自己项目的性质来决定的,选择适合自己项目的接口化程度能减少项目的开发成本。

public interface IContract {
    interface Ui {
        Activity getContextActivity();

        void setLoadingUiVisibility(boolean visibility);
    }

    interface Presenter {

    }
}

IContract只是定义了基本的通用的一些接口,具体到业务模块,通过继承添加自己的业务接口。


IModelAsyncResponse数据响应接口:用于Presenter层响应Model层处理结果。即告诉Model层,我需要T对象。

public interface IModelAsyncResponse<T> {
    void onResponse(T t);

    boolean onFail(Exception e);

    void onCancel();
}



Presenter基类:规范与UI(View层)的绑定与解绑,定义了一些通用的方法。业务模块中通过继承该类实现Presenter层。

public abstract class Presenter<V extends IContract.Ui> {
    protected final String TAG = LogUtils.makeLogTag(this.getClass());
    private UiState mUiState = UiState.UI_STATE_UNDEFINE;

    protected boolean mIsLoadProcessing;

    /**
     * UI的弱引用
     */
    private WeakReference<V> mUiRef;

    /**
     * 关联UI
     *
     * @param ui
     */
    @CallSuper
    public void attachUi(V ui) {
        mUiRef = new WeakReference<V>(ui);
    }

    /**
     * 解除关联
     */
    @CallSuper
    public void detachUi() {
        if (mUiRef != null) {
            onUiState(UiState.UI_STATE_ON_DETACH);
            mUiRef.clear();
        }
    }

    /**
     * 得到UI
     *
     * @return
     */
    public V getUi() {
        return mUiRef.get();
    }

    /**
     * 得到Application
     *
     * @return
     */
    public Application getApplication() {
        return AppUtils.getApplication();
    }

    /**
     * 得到Activity
     *
     * @return
     */
    public Activity getActivity() {
        return getUi().getContextActivity();
    }

    /**
     * 得到Context
     *
     * @return
     */
    public Context getContext() {
        return getUi().getContextActivity();
    }

    /**
     * 得到Intent
     *
     * @return
     */
    public Intent getIntent() {
        if (mUiRef.get() != null) {
            if (mUiRef.get() instanceof Activity) {
                return ((Activity) mUiRef.get()).getIntent();
            } else if (mUiRef.get() instanceof Fragment) {
                return ((Fragment) mUiRef.get()).getActivity().getIntent();
            } else if (mUiRef.get() instanceof android.support.v4.app.Fragment) {
                return ((android.support.v4.app.Fragment) mUiRef.get()).getActivity().getIntent();
            }
        }
        return new Intent();
    }

    public final boolean isUiAlive() {
        if (mUiRef.get() == null) {
            return false;
        }
        if (mUiRef.get() instanceof Activity) {
            return !((Activity) mUiRef.get()).isFinishing();
        }
        return true;
    }

    public final void finishUi() {
        if (mUiRef.get() != null)
            if (mUiRef.get() instanceof Activity) {
                ((Activity) mUiRef.get()).finish();
            } else if (mUiRef.get() instanceof Fragment) {
                ((Fragment) mUiRef.get()).getActivity().finish();
            } else if (mUiRef.get() instanceof android.support.v4.app.Fragment) {
                ((android.support.v4.app.Fragment) mUiRef.get()).getActivity().finish();
            }
    }

    public void setUiLoading(boolean uiLoading) {
        mIsLoadProcessing = uiLoading;
        if (!isUiAlive()) {
            return;
        }
        getUi().setLoadingUiVisibility(uiLoading);
    }

    /**
     * 用于分析传入参数是否非法
     *
     * @return true表示非法, false表示合法
     */
    public boolean parseIntentData(@NonNull Bundle bundle) {
        return false;
    }

    /**
     * 用于分析传入参数是否非法,在View init之后调用
     *
     * @return
     */
    public void afterViewInit() {

    }

    /**
     * UI状态回调
     *
     * @param state UI_STATE_ON_CREATE,UI_STATE_ON_START,UI_STATE_ON_RESUME,UI_STATE_ON_PAUSE,
     *              UI_STATE_ON_STOP,UI_STATE_ON_DETACH
     */
    @CallSuper
    public void onUiState(UiState state) {
        mUiState = state;
    }

    public UiState getUiState() {
        return mUiState;
    }

    public void showShortToast(String message) {
        if (isUiAlive()) {
            Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
        }
    }

    public void showShortToast(@StringRes int resId) {
        if (isUiAlive()) {
            Toast.makeText(getContext(), resId, Toast.LENGTH_SHORT).show();
        }
    }

    public void showLongToast(String message) {
        if (isUiAlive()) {
            Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
        }
    }

    public void showLongToast(@StringRes int resId) {
        if (isUiAlive()) {
            Toast.makeText(getContext(), resId, Toast.LENGTH_LONG).show();
        }
    }

    public enum UiState {
        UI_STATE_UNDEFINE,
        UI_STATE_ON_CREATE,
        UI_STATE_ON_START,
        UI_STATE_ON_RESUME,
        UI_STATE_ON_PAUSE,
        UI_STATE_ON_STOP,
        UI_STATE_ON_DETACH
    }
}



MvpActivity基类:规范与Presenter(Presenter层)的绑定与解绑,定义了一些通用的方法。业务模块中通过继承该类实现View层。
注意:该类继承自笔者自定义的Activity类,而非原生Activity类。自定义的Activity类实现的内容是与架构无关的,所以无需担心。如果直接继承原生Activity类,只需将beforeInitOnCreate, parseIntentData,afterInit三个方法的内容直接按顺序移到原生的Activity的onCreate方法中即可。

public abstract class MvpActivity<V extends IContract.Ui, P extends Presenter<V>>
        extends Activity implements IContract.Ui {
    protected P mPresenter;

    @CallSuper
    @Override
    protected void beforeInitOnCreate(@Nullable Bundle savedInstanceState) {
        // 创建并绑定presenter
        mPresenter = createPresenter();
        if (mPresenter == null) {
            Class presenterClazz;
            Type type = getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                presenterClazz = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
                try {
                    mPresenter = (P) presenterClazz.newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        if (mPresenter != null) {
            mPresenter.attachUi((V) this);
        } else {
            throw new RuntimeException("must initialize a presenter!");
        }
    }

    protected P createPresenter() {
        return null;
    }

    @Override
    protected final boolean parseIntentData() {
        if (mPresenter != null) {
            return mPresenter.parseIntentData(getIntent().getExtras() == null ?
                    new Bundle() : getIntent().getExtras());
        }
        return false;
    }

    @CallSuper
    @Override
    protected void afterInit() {
        if (mPresenter != null) {
            mPresenter.onUiState(Presenter.UiState.UI_STATE_ON_CREATE);
            mPresenter.afterViewInit();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mPresenter != null) {
            mPresenter.onUiState(Presenter.UiState.UI_STATE_ON_START);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mPresenter != null) {
            mPresenter.onUiState(Presenter.UiState.UI_STATE_ON_RESUME);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mPresenter != null) {
            mPresenter.onUiState(Presenter.UiState.UI_STATE_ON_PAUSE);
        }
    }

    @Override
    protected void onStop() {
        if (mPresenter != null) {
            mPresenter.onUiState(Presenter.UiState.UI_STATE_ON_STOP);
        }
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除绑定
        if (mPresenter != null) {
            mPresenter.detachUi();
        }
    }

    @Override
    public android.app.Activity getContextActivity() {
        return this;
    }
}


MVVM模式

先上通用图


MVVM

MVVM是Model-View-ViewModel的简写,即模型-视图-视图模型。【模型】指的是后端传递的数据,【视图】指的是所看到的页面,【视图模型】mvvm模式的核心,它是连接view和model的桥梁。


MVVM模式相对于MVP模式,其实就是将接口化沟通变成了实体化(模型化)沟通。
简单来说就是视图和业务逻辑的沟通方式不再是命令响应(接口化)的方式,而是具体化的实体模型。即VM提供给View的不再是响应型的数据,而是显示整个View的实体模型。View响应的是实体模型的改变,而不再是接口的回调。这样不仅简化原先MVP模式下Presenter层与View层的沟通成本,还将View的显示更加组件化,VM与View的绑定可以形成更微小的显示窗口,以显示窗口的聚合重用来复用代码。
其实从命名就可以看出了,VM即View-model,也就是View的model。

实现MVVM的两个重要的技术:

  1. DataBinding:完成视图实体模型与View绑定的利器。
  2. LiveData:完成视图实体模型的状态化,让业务响应与视图实体模型的状态关联起来。



IModelAsyncResponse数据响应接口:用于VM层响应Model层处理结果。即告诉Model层,我需要T对象。
这部分与MVP类似


ViewModel视图模型:定义了一些基本的显示模型实体。业务模块中通过继承该类实现VM层。

public abstract class ViewModel extends android.arch.lifecycle.ViewModel {
    protected final String TAG = LogUtils.makeLogTag(this.getClass());
    private UiState mUiState = UiState.UI_STATE_UNDEFINE;

    /**
     * UI状态回调
     *
     * @param state UI_STATE_ON_INIT,UI_STATE_ON_RESUME,UI_STATE_ON_PAUSE,
     *              UI_STATE_ON_STOP,UI_STATE_ON_DETACH
     */
    @CallSuper
    public void onUiState(UiState state) {
        mUiState = state;
    }

    public UiState getUiState() {
        return mUiState;
    }

    /**
     * 用于分析传入参数是否非法,在View init之前调用
     *
     * @return true表示非法, false表示合法
     */
    public boolean parseIntentData(@NonNull Bundle bundle) {
        return false;
    }

    /**
     * 在View init之后调用
     *
     * @return
     */
    public void afterViewInit() {

    }

    public void onCleared() {

    }

    MutableLiveData<Integer> observeSyncLiveData = new MutableLiveData<>();

    public MutableLiveData<Integer> getObserveSyncLiveDataData() {
        return observeSyncLiveData;
    }
    
    /**
     * 用于LiveData是其它功能操作返回(不是在VM中初始化赋值)的情况,
     * 在LiveData返回时通过调用setSyncLiveDataTag来告诉UI开始绑定Observer,
     * UI中的必须实现observeSyncLiveData,同时所有其它功能操作返回的LiveData只能在此方法中进行绑定Observer
     *
     * @param liveDataObjTag 用来标识对应的LiveData(由调用者自己确定)
     */
    public void setSyncLiveDataTag(int liveDataObjTag) {
        observeSyncLiveData.setValue(liveDataObjTag);
    }

    // 重置UI
    MutableLiveData<Boolean> resetUiData = new MutableLiveData<>();

    public MutableLiveData<Boolean> getResetUiData() {
        return resetUiData;
    }

    public void resetUi() {
        resetUiData.setValue(true);
    }

    // 结束UI
    MutableLiveData<Boolean> finishData = new MutableLiveData<>();

    public MutableLiveData<Boolean> getFinishData() {
        return finishData;
    }

    public void finishUi() {
        finishData.setValue(true);
    }

    // 加载中ui显示状态
    MutableLiveData<Boolean> uiLoadingData = new MutableLiveData<>();

    public MutableLiveData<Boolean> getUiLoadingData() {
        return uiLoadingData;
    }

    public boolean isUiLoading() {
        return uiLoadingData.getValue();
    }

    public void setUiLoading(boolean isLoading) {
        uiLoadingData.setValue(isLoading);
    }

    // Toast ui显示
    MutableLiveData<String> toastMsgData = new MutableLiveData<>();

    public MutableLiveData<String> getToastMsgData() {
        return toastMsgData;
    }

    public void setToastMsg(String msg) {
        toastMsgData.setValue(msg);
    }

    MutableLiveData<Integer> toastResIdData = new MutableLiveData<>();

    public MutableLiveData<Integer> getToastResIdData() {
        return toastResIdData;
    }

    public void setToastResId(@StringRes Integer id) {
        toastResIdData.setValue(id);
    }
}



MvvmActivity基类:规范与VM的绑定与解绑,定义了一些通用的方法。业务模块中通过继承该类实现View层。
注意:该类继承自笔者自定义的Activity类,而非原生Activity类。自定义的Activity类实现的内容是与架构无关的,所以无需担心。如果直接继承原生Activity类,只需将beforeInitOnCreate,setContentView, findViewOnCreate,parseIntentData,afterInit五个方法的内容直接按顺序移到原生的Activity的onCreate方法中即可。

public abstract class MvvmActivity<T extends ViewDataBinding, VM extends ViewModel> extends Activity {
    protected T mBinding;
    protected VM mViewModel;

    private Observer<Integer> mSyncLiveDataObserver = new Observer<Integer>() {
        @Override
        public void onChanged(@Nullable Integer tag) {
            observeSyncLiveData(tag);
        }
    };

    private Observer<Boolean> mResetUiDataObserver = new Observer<Boolean>() {
        @Override
        public void onChanged(@Nullable Boolean aBoolean) {
            if (aBoolean) {
                finish();
                startActivity(getIntent());
            }
        }
    };

    private Observer<Boolean> mFinishDataObserver = new Observer<Boolean>() {
        @Override
        public void onChanged(@Nullable Boolean aBoolean) {
            if (aBoolean) {
                finish();
            }
        }
    };

    private Observer<Boolean> mUiLoadingDataObserver = new Observer<Boolean>() {
        @Override
        public void onChanged(@Nullable Boolean aBoolean) {
            setLoadingUiVisibility(aBoolean);
        }
    };

    private Observer<String> mToastMsgDataObserver = new Observer<String>() {
        @Override
        public void onChanged(@Nullable String msg) {
            showShortToast(msg);
        }
    };

    private Observer<Integer> mToastResIdDataObserver = new Observer<Integer>() {
        @Override
        public void onChanged(@Nullable Integer resId) {
            showShortToast(resId);
        }
    };

    @CallSuper
    @Override
    protected void beforeInitOnCreate(@Nullable Bundle savedInstanceState) {
        // 创建ViewModel{
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Class presenterClazz = (Class) ((ParameterizedType) type).getActualTypeArguments()[1];
            mViewModel = (VM) ViewModelProviders.of(this).get(presenterClazz);
            mViewModel.getUiLoadingData().setValue(false);
        }
        mViewModel.getObserveSyncLiveDataData().observe(this, mSyncLiveDataObserver);
        mViewModel.getResetUiData().observe(this, mResetUiDataObserver);
        mViewModel.getFinishData().observe(this, mFinishDataObserver);
        mViewModel.getUiLoadingData().observe(this, mUiLoadingDataObserver);
        mViewModel.getToastMsgData().observe(this, mToastMsgDataObserver);
        mViewModel.getToastResIdData().observe(this, mToastResIdDataObserver);
        observeInitLiveData();
    }

    /**
     * 用于在VM中初始化赋值的LiveData的进行监听观察
     * 此方法在Activity onCreate的时候自动调用
     * (注意区别于observeSyncLiveData)
     * observeInitLiveData:用于在VM中初始化的LiveData的进行监听观察。
     * observeSyncLiveData :用于对不是在VM中初始化赋值的LiveData的进行监听观察,需要在VM中主动调用setSyncLiveDataTag。
     */
    public abstract void observeInitLiveData();

    protected void setContentView(Bundle savedInstanceState) {
        mBinding = DataBindingUtil.setContentView(this, getActivityLayoutResId());
    }

    @Override
    protected final void findViewOnCreate() {

    }

    @CallSuper
    @Override
    protected final boolean parseIntentData() {
        if (mViewModel != null) {
            return mViewModel.parseIntentData(getIntent().getExtras() == null ?
                    new Bundle() : getIntent().getExtras());
        }
        return false;
    }

    @CallSuper
    @Override
    protected void afterInit() {
        if (mViewModel != null) {
            mViewModel.onUiState(UiState.UI_STATE_ON_INIT);
        }
        if (mViewModel != null) {
            mViewModel.afterViewInit();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mViewModel != null) {
            mViewModel.onUiState(UiState.UI_STATE_ON_RESUME);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mViewModel != null) {
            mViewModel.onUiState(UiState.UI_STATE_ON_PAUSE);
        }
    }

    @Override
    protected void onStop() {
        if (mViewModel != null) {
            mViewModel.onUiState(UiState.UI_STATE_ON_STOP);
        }
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除绑定
        if (mViewModel != null) {
            mViewModel.onUiState(UiState.UI_STATE_ON_DETACH);
        }
    }

    public void setLoadingUiVisibility(boolean visibility) {

    }

    /**
     * 对不是在VM中初始化赋值的LiveData的进行监听观察(通过其它功能返回的LiveData)。
     * 此方法的调用需要在VM获取到LiveData后中主动调用setSyncLiveDataTag方法。
     * (注意区别于observeInitLiveData)
     * observeInitLiveData:用于在VM中初始化的LiveData的进行监听观察。
     * observeSyncLiveData :用于对不是在VM中初始化赋值的LiveData的进行监听观察,需要在VM中主动调用setSyncLiveDataTag。
     *
     * @param liveDataObjTag 用来标识对应的LiveData(由调用者自己标识)
     */
    public abstract void observeSyncLiveData(int liveDataObjTag);
}



各种模式只是规范化编码方式的一种总结,千万不要为了模式而模式,要根据自己的项目特性和规模,选择合适自己的模式。一般来说模式的开发是针对业务模块的,不同的业务模块根据自己的需求可以使用不同的模式来开发。
比如:
业务重用较多的模块,可以使用MVP模式。
视图重用较多或者业务较复杂的模块,可以使用MVVM模式。
简单的过渡模块,直接Android的默认开发模式就可以了。
不过,一般的模块开发,还是建议使用MVVM模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343