MVC,MVP,MVVM 作为 Android 开发中耳熟能详的三个框架,一直处半懵逼的状态,最近分析了一些谷歌官方给出的samples代码,记录下自己的理解,轻喷~
MVC,MVP,MVVM
MVC
Model View Controller,软件中最常见的一种框架,Controller 层负责操作 Model 数据,并且返回给 View 层进行展示。
从上图看出,用户通过 view 层发出指令到 controller 层,controller 通知 model 层更新数据,model 完成后直接显示在 view 层上。
既然是 Android 猿,那我们从分析下 MVC模型在 Android 中的提现:
- View : layout.xml
- Model: 各种 JavaBean
- Controller : Activity,Fragment 等等
我们来分析一个场景:点击按钮下载文件
按钮是卸载xml里面的,属于view层的
网络请求,数据解析相关代码写在其他类里,比如 netHelper ,属于model层。
view 和 model 通过button.setOnClickListener()关联其来,其卸载activity中,对应于 controller 层。
好像很清晰,看起来没毛病的样子~开始搞事情。。。
如果数据回来需要在view层进行展示,控制某些控件的隐藏/显示,xml文件就无能为力了,我们只能将代码写在 activity 中。activity 就开始找不到自己的归属了,我是谁? view or controller?
如果一个逻辑复杂的页面,维护起来简直是噩梦。
当然从上图也可以看到Model层和View层并没有隔离开,这是一个重要的缺陷。违背了程序的低耦合原则。
MVP
MVP 作为 MVC 的演化,解决了不少 MVC 的问题。对于 android 来说,MVP 的 model 和 MVC 是一样的,而 activity 和 fragment 不再是 controller 层,而是纯粹的 view 层,所有关于用户事件的转发全部交由 presenter 层处理。
优势
看图说话~我们来分析一下,MVP架构是如何解决 MVC 面对的问题:
view 层和 model 层完全解耦。
Presenter 充当桥梁,view 层发出的事件传递到 presenter 层,由P层操作model,获取数据后通知 view 层更新UI。activity 和 fragment 臃肿的问题。
activity 和 fragment 属于 view 层,仅仅是数据回调后更新 UI。在activity中和fragment中没有任何与model相关的逻辑代码,而是将这部分代码放到presenter层中,通过接口的形式将 view 层需要的数据返回。便于测试。
比如如果我们需要测试获取数据功能,只需要实现对应的接口,看presenter是否调用了相应的方法。也可以在presenter中制造假数据,分发给view ,用来测试布局是否正确。逻辑清晰,耦合性低,可维护性提高
缺陷
- 接口过多,一定程度影响我们的编码效率和代码可读性
- 逻辑比较复杂的页面,Activity 代码还是会比较多,当然要比MVC要好的多,而且逻辑清晰
- Presenter 层比较臃肿
最佳实践
- 使用 Fragment 作为 view 层,而 activity 则是一个用于创建 view(fragment)和 presenter 的控制器。
- 根据业务需求抽取基类,定义接口,公共的逻辑尽量抽取,减少代码量。
MVVM
MVVM 最早是由微软提出,先上图:
从 MVVM 架构图分析,MVVM 和 MVP 的区别貌似不大,presenter 换成了 viewmodel层,还有一点就是 view 层和 viewModel 层是binding的关系。viewmodel层的数据发生变化时,view层会相应的更新UI。
DataBinding
DataBinding框架
Android 平台 MVVM 的目前相当火爆,谷歌欧巴的DataBinding框架功不可没。它可以让我们轻松的实现MVVM。看了网上很多的文章,都说DataBinding就是ViewModel层,我有点不同的看法,在这里提出来,大家讨论一下。
众多博文的观点: Android 中 MVVM 就是 Databinding 框架的运用。Android Data Binding中的 ViewModel是根据layout自动生成的Binding类,比如 activity_main.xml ,生成的Binding类就是ActivityMainBinding。
不同观点:ViewModel 是可以进行 binding 的数据模型,Binding 类是View 和 ViewModel 之间的桥梁。ViewModel层数据对象三种形式:
- 继承BaseObservable
- Observable Fields
- Observable Collections
这样定义的数据对象发生改变时,同步更新UI。
DataBinding 框架通过setContentView(int resourceId )
和setXXX()
方法完成View和ViewModel的绑定。
小结:理论知识讲完了,我们总结一下
- 了解区分 MVC,MVP,MVVM
- 初步了解这三种模式在Android中的使用。
- DataBinding 在 MVVM 模式中的职责
其实,真正的最佳实践都是人想出来的,我们并不一定要死磕一种模式。see一下谷歌大大实现的架构。
Android 官方架构分析
Android - architecture介绍
项目地址:android-architecture
Android框架提供了很大的灵活性去创建一个APP。这种灵活性很有价值,但是也让app有很多类,不一致的命名和不规范的框架结构。导致测试,维护,扩展困难。
Android Architecture 演示了帮助解决或避免这些问题的常用方案,该项目用不同的架构实现了同一个APP。当然这些samples仅仅是作为参考,在我们创建自己的app时,还是根据需求选择最合适的。
稳定的samples
正在开发的samples
废话不多说了,下面选择两个我想要在项目中使用的两个架构进行分析:todo-mvp
和todo-mvp-databinding
TODO-MVP架构分析
todo-mvp 是基础的MVP架构,没有使用其他的任何类库。
app设计
该示例项目的代码组织方式完全按照功能组织
- Tasks - 管理tasks列表
- TaskDetail - 查看task详情,并提供删除功能
- AddEditTask - 增加和编辑 tasks
- Statistics - 查看最近的日程
每一个功能内部分为xActivity,xContract,xFragment,xPresenter四个类文件。
- Activity 类,用于创建Fragment 和 Presenter
- Contract 类,这个与之前见到的mvp实现都不同,该类用于统一管理 view 和 presenter 的所有接口,使得 view 和 presenter 中的功能非常清晰。
- Fragment类,View 层 ,实现 view 接口
- Presenter类,Contract类中相应 Presenter 接口的实现类
结构图如下:
代码分析
基类
我们先来看两个Base接口类, BasePresenter 和 BaseView
BasePresenter中含有方法start(),该方法的作用是presenter开始获取数据并调用 view 中的方法更新UI。其调用时机是在 Fragment 类的 onResume() 方法中。
BaseView 中含有方法 setPresenter,该方法作用是将presenter示例传入 view 中,调用时机是 Presenter 实现类的构造函数中。
契约类
之前说过契约类,是与之前所见的MVP实现不同,也是我感觉很优雅的地方。
Activity 的作用
Activity 在项目中是一个控制者,负责创建 view 及 presenter 实例,并将二者进行关联。
MVP 的实现与组织
Presenter 创建时将 Fragment 作为参数传入构造方法
123456public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");// view 调用setPresenter方法,注入 presenter 示例mTasksView.setPresenter(this);}Fragment onResume() 方法中调用 presenter.start()方法
1234567891011121314public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {...@Overridepublic void onResume() {super.onResume();mPresenter.start();}@Overridepublic void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {mPresenter = checkNotNull(presenter);}...
分析代码得出view 和 presenter 相互持有,并且通过构造函数或set方法进行依赖注入。view 处理用户与界面的交互,presenter与model层交互,处理数据回调的逻辑判断,如果需要更新UI,直接调用view的方法,实现了 view 和 presenter 的各司其职。
Model 层设计
Model 最大的特点就是被赋予了获取数据的职责。与我们平常Model只定义Bean对象不同,todo-mvp中,数据的获取,存储,数据状态变化都是model层的任务。Presenter 只需要调用该层的方法并传入回调。
总结:MVP遵循类单一职责的编程原则,但是代码量相对增加,而且view层的代码依然略显臃肿,xml文件作为View层的能力依然很弱,UI的更新依然需要咋Fragment中处理。
MVVM 与 MVP 相结合架构分析
该架构基于 todo-mvp 示例并且使用了 DataBinding 库来展示数据和绑定事件。
它并不是严格的遵守 MVVM 模型 或者 MVP 模型,它同时使用了 ViewModel 和 Presenter 。
app 设计
目录结构与todo-mvp一致。只是多了一个ViewModel。Statistics 和 tasks 模块实现ViewModel的方式并不一样,我们来逐个分析。
Statistics 模块
Statistics 模块
主要类:Activity ,Fragment ,ViewModel,Presenter
StatisticsActivity : 创建 Fragment,ViewModel , Presenter对象,管理注入。
12345678910111213141516171819StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);if (statisticsFragment == null) {statisticsFragment = StatisticsFragment.newInstance();ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),statisticsFragment, R.id.contentFrame);}StatisticsViewModel statisticsViewModel = new StatisticsViewModel(getApplicationContext());// 注入viewModel,用于binding至xml文件statisticsFragment.setViewModel(statisticsViewModel);// viewmodel 实现 view, 构造方法注入到presenterStatisticsPresenter statisticsPresenter = new StatisticsPresenter(Injection.provideTasksRepository(getApplicationContext()), statisticsViewModel);// Fragment 注入 presenter。statisticsFragment.setPresenter(statisticsPresenter);StatisticsFragment : 调用presenter的方法获取数据,绑定 viewmodel 至xml
12345public void setPresenter(@NonNull StatisticsPresenter presenter){mPresenter = checkNotNull(presenter)}mViewDataBinding.setStats(mStatisticsViewModel)
|
|
StatisticsViewModel : 提供get set方法,binding 到 xml 文件,自动更新UI
StatisticsPresenter : 与Model 层交互,获取数据后传递给viewmodel,更新UI
Tasks模块
Tasks 模块 MVP 结构的组织与实现与todo-mvp一致。
主要类: TasksActivity,TasksFragment,TasksPresenter,TasksItemActionHandler,TasksViewModel。
TasksActivity :管理,创建对象
123456789101112131415161718TasksFragment tasksFragment =(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);if (tasksFragment == null) {// Create the fragmenttasksFragment = TasksFragment.newInstance();ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), tasksFragment, R.id.contentFrame);}// Create the presentermTasksPresenter = new TasksPresenter(Injection.provideTasksRepository(getApplicationContext()), tasksFragment);// 创建时注入Presenter,是为了方法的重用。TasksViewModel tasksViewModel =new TasksViewModel(getApplicationContext(), mTasksPresenter);// 注入viewModel,通过Binding类与xml文件关联tasksFragment.setViewModel(tasksViewModel);TasksFragment : View 层,持有ViewModel 和 Presenter。将ViewModel绑定至xml文件,根据数据自动更新UI,调用Presenter的方法获取或更新数据,处理用户和组件的交互。
12345TasksFragBinding tasksFragBinding = TasksFragBinding.inflate(inflater, container, false);// 绑定viewmodel 和 xml文件,自动更新UI。tasksFragBinding.setTasks(mTasksViewModel);// 将presenter与xml绑定,作为事件处理类,重用方法tasksFragBinding.setActionHandler(mPresenter);TasksPresenter : 和 todo-mvp 的presenter 没什么区别,获取数据,然后调用view的方法,更新UI。
TasksViewModel : 继承BaseObservable,在与Xml文件绑定后,内容发生改变时刻自动更新UI。
注意@Bindable
注解和notifyPropertyChanged()
的使用;1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071public class TasksViewModel extends BaseObservable {int mTaskListSize = 0;private final TasksContract.Presenter mPresenter;private Context mContext;public TasksViewModel(Context context, TasksContract.Presenter presenter) {mContext = context;mPresenter = presenter;}@Bindablepublic String getCurrentFilteringLabel() {switch (mPresenter.getFiltering()) {case ALL_TASKS:return mContext.getResources().getString(R.string.label_all);case ACTIVE_TASKS:return mContext.getResources().getString(R.string.label_active);case COMPLETED_TASKS:return mContext.getResources().getString(R.string.label_completed);}return null;}@Bindablepublic String getNoTasksLabel() {switch (mPresenter.getFiltering()) {case ALL_TASKS:return mContext.getResources().getString(R.string.no_tasks_all);case ACTIVE_TASKS:return mContext.getResources().getString(R.string.no_tasks_active);case COMPLETED_TASKS:return mContext.getResources().getString(R.string.no_tasks_completed);}return null;}@Bindablepublic Drawable getNoTaskIconRes() {switch (mPresenter.getFiltering()) {case ALL_TASKS:return mContext.getResources().getDrawable(R.drawable.ic_assignment_turned_in_24dp);case ACTIVE_TASKS:return mContext.getResources().getDrawable(R.drawable.ic_check_circle_24dp);case COMPLETED_TASKS:return mContext.getResources().getDrawable(R.drawable.ic_verified_user_24dp);}return null;}@Bindablepublic boolean getTasksAddViewVisible() {return mPresenter.getFiltering() == ALL_TASKS;}@Bindablepublic boolean isNotEmpty() {return mTaskListSize > 0;}public void setTaskListSize(int taskListSize) {mTaskListSize = taskListSize;notifyPropertyChanged(BR.noTaskIconRes);notifyPropertyChanged(BR.noTasksLabel);notifyPropertyChanged(BR.currentFilteringLabel);notifyPropertyChanged(BR.notEmpty);notifyPropertyChanged(BR.tasksAddViewVisible);}}TasksItemActionHandler : 绑定至xml文件,通过set方法注入,传入Presenter,可以重用方法。
1234567891011121314151617181920212223242526public class TasksItemActionHandler {private TasksContract.Presenter mListener;public TasksItemActionHandler(TasksContract.Presenter listener) {mListener = listener;}/*** Called by the Data Binding library when the checkbox is toggled.*/public void completeChanged(Task task, boolean isChecked) {if (isChecked) {mListener.completeTask(task);} else {mListener.activateTask(task);}}/*** Called by the Data Binding library when the row is clicked.*/public void taskClicked(Task task) {mListener.openTaskDetails(task);}}
总结:该架构节省去了findViewById的苦力活,通过Binding库和ViewModel层增强了XMl的功能。
相比MVP,view层中的代码减少,UI展式改变只需改变ViewModel层。
项目MVVM,业务逻辑代码放在了Presenter中,明确了各层的职责。
这也是我比较倾向在项目中使用的架构。
写在最后
作为程序猿,纸上来的终觉浅,既然觉得浅了,咱们就去玩一些深的,自己去实现以下MVC,MVP,MVVM。实践出真知,真正的理解这三种架构。敬请期待~