最近研读了许多关于Android架构的分享资料,同时查阅了Android官方MVP架构案例的源码,获益良多,在这以一个简单的Demo谈谈我对MVP架构理解,以及和传统MVC架构的对比。
一、需求
- 在一个Activity界面中有一个Button和一个TextView,用户点击Button后,模拟从服务器请求数据,并将数据更新到TextView中显示(本案例非常简单,简单到让人不想往下看,但道理是相通的,该例子已足以说明问题)
-
界面大致长这样:
二、方案
- Android中常见的架构有很多:MVC、MVP、MVVM、Clean、AAC等等,但目前最实用的还属MVP,因此本文重点对比MVC与MVP的实现方式及其优缺点
MVC
-
MVC全称:Model-View-Controller
- Model层:对应的是数据源以及数据结构等相关对象,一般数据的来源有本地数据库和远程服务器,主要负责网络请求,数据库处理,I/O操作
- View层:对应的是xml布局文件和Java代码动态view部分,主要负责数据的展示与接收用户操作
- Controller层:对应的是Activity/Fragment,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多
-
MVC架构图如下:
工作原理:当用户触发UI事件的时候,View层会发送请求到Controller层,接着Controller层去通知Model层处理数据,Model层处理完数据后直接显示在View层上
使用MVC实现上述需求,具体代码如下:
//Controller层+View层
public class MVCActivity extends AppCompatActivity {
private Button mMvcBtn;
private TextView mMvcTv;
private MVCModel model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mMvcBtn = findViewById(R.id.btn_mvc);
mMvcTv = findViewById(R.id.tv_mvc);
model = new MVCModel();
mMvcBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// OnClickListener是属于view的,这里View直接访问了model
model.getData(new Callback1() {
@Override
public void onCallback(String s) {
updateText(s);
}
});
}
});
}
//该方法属于Controller
private void updateText(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mMvcTv.setText(text);
}
});
}
}
//Model层
public class MVCModel implements BaseModel {
public void getData(final Callback1 callback1) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
//模拟网络请求
String msg = "来自网络的数据: " + new Random().nextInt(100);
callback1.onCallback(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
代码很简单,当点击Button的时候,调用View的onClick方法,该方法直接调用Model的getData方法来请求数据,当请求到数据之后,回调Activity的updateText方法,最终调用View的setText方法更新TextView的展示,由于使用了回调,一定程度上降低了View层和Model层的耦合。这里并没有完全按照上诉架构图实现,原因是这里的Activity既充当Controller层又是View层,无法明显区分,后续Activity会演变得很臃肿,代码量会很庞大
-
整个过程的流程图如下:
从上面的流程图可以看出,View是可以直接访问Model的,从而View中会包含Model信息,不可避免的还要包括一些业务逻辑,因此Model和View之间是耦合的。同时Controller往往不仅要处理ui的呈现与事件的响应,而且还要负责和model的通信,三者之间强强关联,造成代码耦合
-
总结下MVC架构的优缺点:
- Android开发中默认使用的框架,简单通用,易于上手,开发效率快
- 具有一定分层,model彻底解耦,controller和view并没有解耦,逻辑比较混乱,导致Activity 或Fragment很臃肿
- 开发难以维护,没有中间件接口做缓冲,难以替换底层的实现
- 各层之间耦合较高,可测试性较差,不利于分层测试的开展
MVP
-
MVP 全称:Model-View-Presenter
- Model层:对应的是数据源以及数据结构等相关对象,一般数据的来源有本地数据库和远程服务器,主要负责网络请求,数据库处理,I/O操作
- View层:对应的不只是layout中的xml文件,还包括了Activity/Fragment作为视图的显示,这里的Activity专心只做视图相关的操作,比如UI布局,数据渲染,点击按钮交互等
- Presenter层:对应的是控制器等相关对象,负责View层和Model层之间的通信,控制业务逻辑的调用
-
MVP架构图如下:
工作原理:当出现View事件的响应或者生命周期变化时,首先去找Presenter,然后Presenter去找Model请求数据,Model获取到数据之后通知Presenter,Presenter再通知View更新数据展示
在view和presenter两者之间的通信并不是想怎么调用就可以怎么调用的,他们之间有着一个标准的协议,就是在两者之间定义通用接口IContract,在这个接口中定义了view层中要暴露的接口,也定义了presenter层中需要暴露给view的接口,利用接口的方式将两者进行隔离,达到面向接口编程的目的
使用MVP实现上述需求,具体代码如下:
- View和Presenter之间的接口契约类:MVPContract.java
public interface MVPContract {
interface IMVPModel extends BaseModel {
void getData(Callback1 callback);
}
interface IMVPView extends BaseView {
void updateText(String text);
void showToast(String msg);
}
interface IMVPPresenter extends BasePresenter {
void request();
}
}
- 契约类用于定义同一个界面的View接口和Presenter的具体实现,这样做的主要好处有:
- 通过规范的方法命名和注释可以清晰的看到整个页面的逻辑,提高代码的可读性
- 便于单元测试,通过Mock对象实现契约接口,可分别传入View和Presenter中进行测试
- 低耦合使得变更逻辑更容易,同时可以复用View或Presenter对象
- View层:MVPActivity.java
public class MVPActivity extends BaseActivity implements MVPContract.IMVPView {
private Button mMvpBtn;
private TextView mMvpTv;
@Override
protected void initUI(Bundle savedInstanceState) {
setContentView(R.layout.activity_mvp);
mMvpBtn = findViewById(R.id.btn_mvp);
mMvpTv = findViewById(R.id.tv_mvp);
mMvpBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPresenter != null) {
//这里将用户请求转发给Presenter
mPresenter.request();
}
}
});
}
...
@Override
protected MVPContract.IMVPPresenter createPresenter() {
return new MVPPresenter();
}
@Override
public void setPresenter(MVPContract.IMVPPresenter presenter) {
mPresenter = presenter;
}
@Override
public void updateText(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mMvpTv.setText(text);
}
});
}
@Override
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
- 这里Activity只处理UI逻辑,代码变得更加简洁,同时在父类BaseActivity中管理生命周期任务
public abstract class BaseActivity> extends AppCompatActivity {
protected P mPresenter;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
mPresenter.attachView((V) this);
initUI(savedInstanceState);
initParams();
initData();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mPresenter != null) {
mPresenter.onSaveInstanceState(outState);
}
}
@Override
public void onResume() {
super.onResume();
if (mPresenter != null) {
mPresenter.onStart();
}
}
@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.onDestroy();
}
super.onDestroy();
}
protected abstract P createPresenter();
protected abstract void initUI(Bundle savedInstanceState);
protected abstract void initParams();
protected abstract void initData();
}
- Presenter层:MVPPresenter.java
public class MVPPresenter extends AbstractPresenter implements MVPContract.IMVPPresenter {
@Override
protected MVPContract.IMVPModel createModel() {
return new MVPModel();
}
@Override
public void request() {
if (model != null) {
//这里请求Model层拉取数据
model.getData(new Callback1() {
@Override
public void onCallback(String s) {
if (isViewAttached()) {
getView().updateText(s);
}
}
});
}
}
}
public interface BasePresenter {
void onStart();
void onDestroy();
void onSaveInstanceState(Bundle outState);
void attachView(V view);
void detachView();
}
- BasePresenter中定义了通用的接口方法,例如onStart()、attachView()等方法,同时为了代码复用,提供了抽象实现AbstractPresenter.java
public abstract class AbstractPresenter implements BasePresenter {
protected WeakReference viewRef;
protected M model;
public AbstractPresenter() {
model = createModel();
}
@Override
public void onStart() {
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
@Override
public void onDestroy() {
detachView();
model.onDestroy();
}
@Override
public void attachView(V v) {
viewRef = new WeakReference(v);
viewRef.get().setPresenter(this);
}
@Override
public void detachView() {
if (viewRef != null) {
viewRef.clear();
viewRef = null;
}
}
public void setModel(M m) {
model = m;
}
protected V getView() {
return viewRef == null ? null : viewRef.get();
}
protected boolean isViewAttached() {
return viewRef != null && viewRef.get() != null;
}
protected abstract M createModel();
}
- Model层:MVPModel.java
//这里的Model层实现和MVC中的没有任何差别
public class MVPModel implements MVPContract.IMVPModel {
@Override
public void getData(final Callback1 callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
//模拟从网络拉取数据
String msg = "来自网络的数据: " + new Random().nextInt(100);
callback.onCallback(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
从代码上看可以发现比起传统的MVC,从代码数量上看似乎并没有减少反而增加了不少的代码和接口,但是逻辑调用更加清晰,各层之间的耦合度更低
在MVPActivity中实现了MVPContract.IMVPView接口,并实现了updateText()和showToast()两个方法,这两个方法都属于UI逻辑。同时在父类的onCreate()的时候创建了一个presenter对象,并将自身绑定到presenter对象中。在onResume()的时候调用了presenter.onStart()方法,然后在onDestroy()的时候调用了presenter.onDestroy()方法。而当onClick事件响应的时候也只是转发请求presenter.request()方法,其余业务逻辑一概不理
在MVPPresenter中实现了MVPContract.IMVPPresenter接口并实现了request()方法,通过从父类继承的attachView()方法绑定View,同时将自身传给View,实现两者之间的绑定。当Model获取数据后回调Presenter后,Presenter将调用了view的updateText方法,以此来对View视图的UI呈现以及交互做出相应的响应。而最后的onDestroy()方法则是用与释放对View的循环引用资源的,避免内存泄漏
-
MVP架构流程图如下:
从上面流程图中可以看出,MVP中去除了View和Model之间的调用关系,从而彻底的分离了Model和View之间的关联与耦合,这样Model和View就不会直接交互了,所有的交互都由Presenter进行,Presenter充当了桥梁的角色,业务调用逻辑更加清晰明确
-
总结下MVP架构的优缺点:
- 符合高内聚低耦合软件设计要求,并且尽可能的保证了开闭原则
- Model与View完全分离,便于并行开发,大大降低维护和交接成本
- Presener和View均可复用,一个Presener可以用于多个View,而不需要改变Presenter的逻辑,并且View可以进行组件化,提供调用接口即可
- 代码结构清晰,通过接口隔离,代码可测试性高,便于开展分层测试
- 对于很小的Demo来说构建复杂和麻烦,不适合短期、小型且以后不在做任何维护的模块开发
- 由于将所有业务逻辑都放在Presener层,Presener可能会变得笨重
三、对比
- 下面从几个方面对比下MVC与MVP,可以看出MVP基本完胜
架构 | 开发速度 | 难度 | 低耦合 | 流行度 | 可测性 | 代码复用 |
---|---|---|---|---|---|---|
MVC | 5星 | 1星 | 2星 | 1星 | 2星 | 1星 |
MVP | 4星 | 3星 | 4星 | 4星 | 4星 | 4星 |
四、总结
- 架构选型没有孰优孰劣,需要结合项目状况选择最合适的,下面是一般性经验:
- 不要为了使用设计模式或架构方法而使用
- 如果项目简单,没什么复杂性,未来改动也不大的话,建议使用MVC
- 对于工具类或者业务逻辑较重的产品级app,建议使用MVP