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 和 BaseView1
2
3public interface BasePresenter{
void start();
}
BasePresenter中含有方法start(),该方法的作用是presenter开始获取数据并调用 view 中的方法更新UI。其调用时机是在 Fragment 类的 onResume() 方法中。1
2
3public interface BaseView<T>{
void setPresenter(T presenter);
}
BaseView 中含有方法 setPresenter,该方法作用是将presenter示例传入 view 中,调用时机是 Presenter 实现类的构造函数中。
契约类
之前说过契约类,是与之前所见的MVP实现不同,也是我感觉很优雅的地方。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23view 和 presenter 的职责很明了,增删改都很方便
public interface TaskDetailContract {
interface View extends BaseView<Presenter> {
void setLoadingIndicator(boolean active);
void showMissingTask();
...
}
interface Presenter extends BasePresenter {
void editTask();
void deleteTask();
void completeTask();
void activateTask();
}
}
Activity 的作用
Activity 在项目中是一个控制者,负责创建 view 及 presenter 实例,并将二者进行关联。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame);
if (taskDetailFragment == null) {
taskDetailFragment = TaskDetailFragment.newInstance(taskId);
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
taskDetailFragment, R.id.contentFrame);
}
// Create the presenter
new TaskDetailPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
MVP 的实现与组织
Presenter 创建时将 Fragment 作为参数传入构造方法
1
2
3
4
5
6public 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()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
...
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public 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对象,管理注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19StatisticsFragment 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, 构造方法注入到presenter
StatisticsPresenter statisticsPresenter = new StatisticsPresenter(
Injection.provideTasksRepository(getApplicationContext()), statisticsViewModel);
// Fragment 注入 presenter。
statisticsFragment.setPresenter(statisticsPresenter);StatisticsFragment : 调用presenter的方法获取数据,绑定 viewmodel 至xml
1
2
3
4
5public void setPresenter(@NonNull StatisticsPresenter presenter){
mPresenter = checkNotNull(presenter)
}
mViewDataBinding.setStats(mStatisticsViewModel)
1 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> |
StatisticsViewModel : 提供get set方法,binding 到 xml 文件,自动更新UI
StatisticsPresenter : 与Model 层交互,获取数据后传递给viewmodel,更新UI
Tasks模块
Tasks 模块 MVP 结构的组织与实现与todo-mvp一致。
主要类: TasksActivity,TasksFragment,TasksPresenter,TasksItemActionHandler,TasksViewModel。
TasksActivity :管理,创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = 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的方法获取或更新数据,处理用户和组件的交互。
1
2
3
4
5TasksFragBinding 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()
的使用;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71public 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;
}
@Bindable
public 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;
}
@Bindable
public 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;
}
@Bindable
public 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;
}
@Bindable
public boolean getTasksAddViewVisible() {
return mPresenter.getFiltering() == ALL_TASKS;
}
@Bindable
public 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,可以重用方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public 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。实践出真知,真正的理解这三种架构。敬请期待~
author:@ygwang