MVC、MVP、MVVM,作为Android开发中重要的三种框架模式,能够理解并能较好地将其运用在自己的项目中是很重要的。本文主要是自己对Android中这三种框架模式的学习总结,其中重点介绍了MVP模式,如有错误或者表达不当之处,欢迎指出。
一、概述
二、MVC
三、MVP
1.简介
2.示例
3.改进
四、MVVM
五、参考
一、概述
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。常见的有单例模式,适配器模式,代理模式等等。MVC,MVP等等不属于设计模式,而是框架模式,那么框架模式与设计模式的区别是什么呢。
简单来说,框架模式是一种思想,框架是框架模式的具体实现,是对代码的重用,而设计模式是设计重用。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述,它比框架更抽象;框架可以用代码表示,也能直接执行或复用,而对模式而言只有实例才能用代码表示;设计模式是比框架更小的元素,一个框架中往往含有一个或多个设计模式,框架总是针对某一特定应用领域,但同一模式却可适用于各种应用。可以说,框架是软件,而设计模式是软件的知识。
二、MVC
MVC全称是Model-View-Controller,即模型-视图-控制器,它用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC三部分具体如下:
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责从网络中或者在数据库中存取数据。View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC的目的主要是将数据模型和视图分离开,并以控制器作为连接二者的桥梁以实现解耦,下面这张图很清楚地展示了整体结构:
在Android开发中,MVC的应用比较经典,比如layout文件夹下的各种布局文件就对应于view层,从网络或者数据库获取数据就对应于model层,controller层就是各种activity。
三、MVP
MVP,全称是Model-View-Presenter,它针对MVC做了一些改进,解除了Android中View和Model的耦合,使得写出的代码逻辑更加清晰,可扩展性更好。
简介
在Android中,传统的MVC中的View,对应的是各种Layout布局文件,但是这些布局文件中并不像Web端那样强大,能做的事情非常有限。Controller对应的是Activity,但是在实际的项目中也会有很多UI操作在这一层,做了很多View中应该做的事情,当然Controller中也包含Controller应该做的事情,比如各种事件的派发回调,而且在一层中我们会根据事件再去调用Model层操作数据,所以这种MVC的方式在实际项目中,Activity所在的Controller是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。
MVC的进化版——MVP把Layout布局和Activity作为View层,增加了Presenter,Presenter层与Model层进行业务的交互,完成后再与View层交互(也就是Activity)进行回调来刷新UI。这样一来,所有业务逻辑的工作都交给了Presenter中进行,使得View层与Model层的耦合度降低,Activity中的工作也进行了简化。下面通过一个简单的例子来展示一些它的用法,当然想要灵活地掌握MVP这种框架模式还是需要不断地实践的。
示例
在这个例子中,我们的主要功能是在一个Activity中获取新闻数据,并将其展示在页面上,下面是几部分主要的代码。
- Model层
1、接口NewsModel
public interface NewsModel {
//给定新闻数据的url获取数据
void getNewsFromNet(String url, NewsModelImpl.NewsCallBack newsCallBack);
}
2、NewsModelImpl实现数据获取
public class NewsModelImpl implements NewsModel {
private String newsData;
@Override
public void getNewsFromNet(final String url, final NewsCallBack newsCallBack) {
//模拟获取数据耗时操作
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟从url获取数据
newsData = url + "湄公河票房突破十亿 1天前";
newsCallBack.onSuccess(newsData);
}
}.start();
}
public interface NewsCallBack {
void onSuccess(String mNewsData);
}
}
- Presenter层
public class NewsPresenter {
//View的引用
private NewsView mNewsView;
//Model的引用
private NewsModel mNewsModel = new NewsModelImpl();
private Handler handler = new Handler();
public NewsPresenter(NewsView mNewsView) {
this.mNewsView = mNewsView;
}
public void getNewsData() {
mNewsView.showLoading();
mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
@Override
public void onSuccess(final String mNewsData) {
handler.post(new Runnable() {
@Override
public void run() {
mNewsView.displayNews(mNewsData);
mNewsView.dismissLoading();
}
});
}
});
}
}
- View层
1、逻辑抽象
public interface NewsView {
//加载中
void showLoading();
//展示新闻
void displayNews(String newsData);
//取消加载
void dismissLoading();
//加载失败
void displayFailing();
}
2、具体实现
public class NewsActivity extends AppCompatActivity implements NewsView {
private TextView tvData;
private String data;
private ProgressDialog progressDialog;
private NewsPresenter newsPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvData = (TextView) findViewById(R.id.tv_data);
newsPresenter = new NewsPresenter(this);
newsPresenter.getNewsData();
}
@Override
public void showLoading() {
progressDialog = ProgressDialog.show(this, "请稍候", "数据正在加载中", true, false);
progressDialog.show();
}
@Override
public void displayNews(String newsData) {
data = newsData;
tvData.setText(data);
}
@Override
public void dismissLoading() {
progressDialog.dismiss();
}
@Override
public void displayFailing() {
Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
}
}
上面代码的主要逻辑如下:
1、Model层主要是数据获取,我们定义了接口NewsModel,并在NewsModelImpl中实现数据获取,注意这里回调机制的用法。
2、View层的话我们先定义接口NewsView,抽象出通用的方法。
3、Presenter层的话,作为Model和View的桥梁,持有NewsModel和NewsView的引用,NewsPresenter主要完成业务逻辑的处理。
4、最后我们再看下NewsActivity,实现了接口NewsView,并在onCreate()方法里通过newsPresenter = new NewsPresenter(this);将自身传递给NewsPresenter建立引用,这样M、V、P三者的联系就建立起来了。
改进
上面的三部分代码给出了MVP模式的一个典型的例子,但是还是存在一定的问题的。考虑如下:在上面的NewsPresenter中主要是一些业务逻辑的处理,其中getNewsData()是一个较为耗时的操作,因为NewsPresenter持有了对NewsActivity的强引用,如果在请求结束之前NewsActivity被销毁了,因为网络请求还没有返回,导致NewsPresenter一直持有NewsActivity对象,使得NewsActivity对象无法被回收,发生内存泄漏。解决方法主要是** 对presenter和view层进行修改 **。在上面代码的基础上,我们做出下面的修改。
- 添加BasePresenter
BasePresenter主要是建立Presenter和View之间的联系,方便控制二者的关系。
public abstract class BasePresenter {
private Reference mViewReference;
//建立关联
public void attachView(T view) {
mViewReference = new WeakReference<>(view);
}
//获取关联
protected T getView() {
return mViewReference.get();
}
//是否关联
public boolean isViewAttached() {
return mViewReference != null && mViewReference.get() != null;
}
//解除关联
public void detachView() {
if (mViewReference != null) {
mViewReference.clear();
mViewReference = null;
}
}
}
- 修改NewsPresenter 使其继承自BasePresenter
public class NewsPresenter extends BasePresenter {
private NewsModel mNewsModel;
private Handler handler;
public NewsPresenter() {
this.mNewsModel = new NewsModelImpl();
this.handler= new Handler();
}
public void getNewsData() {
//getView()获取已经关联的View
getView().showLoading();
mNewsModel.getNewsFromNet("", new NewsModelImpl.NewsCallBack() {
@Override
public void onSuccess(final String mNewsData) {
handler.post(new Runnable() {
@Override
public void run() {
getView().displayNews(mNewsData);
getView().dismissLoading();
}
});
}
});
}
}
- 添加BaseActivity
在onCreate()里将View与Presenter关联,onDestroy()里解除关联。
public abstract class BaseActivity> extends AppCompatActivity {
protected T mPresenter;
@SuppressWarnings("unchecked")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = setPresenter();
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.detachView();
}
protected abstract T setPresenter();
}
- 最后是修改后的NewsActivity
public class NewsActivity extends BaseActivity implements NewsView {
private TextView tvData;
private String data;
private ProgressDialog progressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvData = (TextView) findViewById(R.id.tv_data);
mPresenter.getNewsData();
}
@Override
public void showLoading() {
progressDialog = ProgressDialog.show(this, "请稍候", "数据正在加载中", true, false);
progressDialog.show();
}
@Override
public void displayNews(String newsData) {
data = newsData;
tvData.setText(data);
}
@Override
public void dismissLoading() {
progressDialog.dismiss();
}
@Override
public void displayFailing() {
Toast.makeText(this, "加载失败", Toast.LENGTH_SHORT).show();
}
@Override
protected NewsPresenter setPresenter() {
return new NewsPresenter();
}
}
到这里,MVP的部分介绍就结束了。MVP是开发过程中比较好的框架模式,它能够将各个组件进行解耦,并且带来良好的可扩展性、可测试性、稳定性、可维护性,同时使得每个类型的职责相对单一、简单,有效地将业务逻辑和数据处理等工作从Activity里抽离出来,使得每个类尽可能简单。
四、MVVM
MVVM与MVP非常相似,唯一的区别在于View和Model进行双向绑定,两者之间有一方发生变化则会反应到另一方上。MVP中的View更新需要通过Presenter。Android中的ListView、Adapter以及数据之间的关系就像MVVM,其中Adapter就是ViewModel角色,它与View进行了绑定,又与数据集进行了绑定,当数据集合发生变化时,调用Adapter的notifyDataSetChanged之后View就直接更新,它们之间没有直接的耦合,使得ListView变得更为灵活。
五、参考
到这里,关于常见的几种框架模式就介绍完了,这里也只是自己的初步学习总结,在之后的开发实践中会继续完善,争取带来更完善的总结与思考。
相关参考:
1、何红辉 《Android源码设计模式 解析与实战》
2、MVP框架Mosby架构详解
3、Hongyang - 浅谈 MVP in Android