在平常开发APP 的过程当中,随着业务的扩展,规模的变化。咱们的代码规模也会逐渐变得庞大,每个类里的代码也会逐渐增多。尤为是Activity和Fragment ,因为Context 的存在,基本上全部对视图的操做咱们只能在Activity和Fragment中完成;即使是对某些逻辑进行封装,Activity和Fragment 依旧会显得过于臃肿。所以,咱们须要换一种思路去写代码,这个时候MVP模式就应用而生了!那么MVP 怎么用呢,下面就来讲一说。java
假设你如今如要实现下图中的功能:android
这个需求很简单,就是点击按钮,下载一张图片,显示下载进度;下载完成后,在ImageView中显示这张图片。
下面咱们就分别用传统的方式(也就是所谓的MVC)和MVP 模式分别取实现这个功能。而后分析一下MVP 到底好在哪里。git
public class MVCActivity extends AppCompatActivity {
private Context mContext;
private ImageView mImageView;
private MyHandler mMyHandler;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
mContext = this;
init();
}
private void init() {
//view init
mImageView = (ImageView) findViewById(R.id.image);
mMyHandler = new MyHandler();
progressDialog = new ProgressDialog(mContext);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.dismiss();
}
});
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setTitle("下载文件");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
//click-event
findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressDialog.show();
HttpUtil.HttpGet(Constants.DOWNLOAD_URL, new DownloadCallback(mMyHandler));
}
});
findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressDialog.show();
HttpUtil.HttpGet(Constants.DOWNLOAD_ERROR_URL, new DownloadCallback(mMyHandler));
}
});
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 300:
int percent = msg.arg1;
if (percent < 100) {
progressDialog.setProgress(percent);
} else {
progressDialog.dismiss();
Glide.with(mContext).load(Constants.LOCAL_FILE_PATH).into(mImageView);
}
break;
case 404:
progressDialog.dismiss();
Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
}复制代码
用mvc的方式,一个Activity就能搞定。代码逻辑很简单,点击按钮后显示以前初始化好ProgressDialog,而后开始下载任务(这里HttpUtil 内部简单封装了OKHttp 的异步GET请求,实现下载文件保存到本地的功能,实现细节在此不作深刻探讨,有兴趣的同窗能够查看源码),而后将请求结果经过Handler返回,在handleMessage中根据返回数据的信息作出不一样的UI 处理;下载成功时在ImageView中显示图片,下载失败时Toast提示。github
能够发现,在这种状况以前,Activity的任务十分繁重,既要负责下载任务的具体实施,还要根据下载进行再次的逻辑判断,才能去更新UI。这里只是一个简单的任务,你可能以为无所谓,可是实际开发中,一个Activity中有许多的交互事件,这个时候Activity的代码就显得特别的庞大;一旦需求变动或出现bug,那简直就是噩梦一场。网络
所以,咱们但愿Activity能够变成下面这样架构
具体到上面的demo里就是,Activity负责发起下载任务,可是不负责具体实现;何时显示ProgressDialog,显示多少?何时提示错误信息,这一切都但愿有个东西能直接告诉Activity,而再也不是在Activity里再作判断。怎样才能作到呢?那就得靠MVP 了。mvc
MVP 模式所作的事情很简单,就是将业务逻辑和视图逻辑抽象到接口中。异步
怎么理解呢,咱们就根据这次要实现的下载功能,用代码说话。ide
Model 接口定义全部须要实现的业务逻辑,在咱们的下载任务中,业务逻辑只有一个,就是下载;所以Model 接口能够这么定义 :布局
public interface IDownloadModel {
/** * 下载操做 * @param url */
void download(String url);
}复制代码
View 接口定义全部须要实现的视图逻辑,在咱们的下载任务中,视图逻辑包括
所以View接口能够这么定义:
public interface IDownloadView {
/** * 显示进度条 * @param show */
void showProgressBar(boolean show);
/** * 设置进度条进度 * @param progress */
void setProcessProgress(int progress);
/** * 根据数据设置view * @param result */
void setView(String result);
/** * 设置请求失败时的view */
void showFailToast();
}复制代码
Presenter 接口做为链接Model和View的中间桥梁,须要将两者链接起来,所以他须要完成如下工做:
所以,Presenter 就能够这么定义:
public interface IDowndownPresenter {
/** * 下载 * @param url */
void download(String url);
/** * 下载成功 * @param result */
void downloadSuccess(String result);
/** * 当前下载进度 * @param progress */
void downloadProgress(int progress);
/** * 下载失败 */
void downloadFail();
}复制代码
上面实现了,各个接口的定义,下面来看看他们具体的实现:
public class DownloadModel implements IDownloadModel {
private IDowndownPresenter mIDowndownPresenter;
private MyHandler mMyHandler = new MyHandler();
public DownloadModel(IDowndownPresenter IDowndownPresenter) {
mIDowndownPresenter = IDowndownPresenter;
}
@Override
public void download(String url) {
HttpUtil.HttpGet(url, new DownloadCallback(mMyHandler));
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 300:
int percent = msg.arg1;
if (percent < 100) {
mIDowndownPresenter.downloadProgress(percent);
} else {
mIDowndownPresenter.downloadSuccess(Constants.LOCAL_FILE_PATH);
}
break;
case 404:
mIDowndownPresenter.downloadFail();
break;
default:
break;
}
}
}
}复制代码
在MVP模式中,Model的工做就是完成具体的业务操做,网络请求,持久化数据增删改查等任务。同时Model中又不会包含任何View。
这里Model的具体实现很简单,将Http任务的结果返回到Handler当中,而在Handler中的实现又是由Presenter完成。
那么Presenter接口又是怎样实现的呢?赶忙来看看
public class DownloadPresenter implements IDowndownPresenter {
private IDownloadView mIDownloadView;
private IDownloadModel mIDownloadModel;
public DownloadPresenter(IDownloadView IDownloadView) {
mIDownloadView = IDownloadView;
mIDownloadModel = new DownloadModel(this);
}
@Override
public void download(String url) {
mIDownloadView.showProgressBar(true);
mIDownloadModel.download(url);
}
@Override
public void downloadSuccess(String result) {
mIDownloadView.showProgressBar(false);
mIDownloadView.setView(result);
}
@Override
public void downloadProgress(int progress) {
mIDownloadView.setProcessProgress(progress);
}
@Override
public void downloadFail() {
mIDownloadView.showProgressBar(false);
mIDownloadView.showFailToast();
}
}复制代码
能够看到,咱们在DownloadPresenter的构造方法中,同时实例化了Model和View,这样Presenter中就同时包含了二者;
这样;在Presenter具体实现中,业务相关的操做由Model去完成(例如download),视图相关的操做由View去完成
(如setView等)。Presenter 做为桥梁的做用就这样体现出来了,巧妙的将View和Model的具体实现链接了起来。
最后再看一下View接口的具体实现,也就是Activity的实现:
public class MVPActivity extends AppCompatActivity implements IDownloadView {
private Context mContext;
private ImageView mImageView;
private ProgressDialog progressDialog;
private DownloadPresenter mDownloadPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
setContentView(R.layout.activity_mvp);
init();
}
private void init() {
mDownloadPresenter = new DownloadPresenter(this);
//view init
mImageView = (ImageView) findViewById(R.id.image);
findViewById(R.id.downloadBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDownloadPresenter.download(Constants.DOWNLOAD_URL);
}
});
findViewById(R.id.downloadBtn1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDownloadPresenter.download(Constants.DOWNLOAD_ERROR_URL);
}
});
progressDialog = new ProgressDialog(mContext);
progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancle", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.dismiss();
}
});
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setTitle("下载文件");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
}
@Override
public void showProgressBar(boolean show) {
if (show) {
progressDialog.show();
} else {
progressDialog.dismiss();
}
}
@Override
public void setProcessProgress(int progress) {
progressDialog.setProgress(progress);
}
@Override
public void setView(String result) {
Glide.with(mContext).load(result).into(mImageView);
}
@Override
public void showFailToast() {
Toast.makeText(mContext, "Download fail !", Toast.LENGTH_SHORT).show();
}
}复制代码
在点下按钮执行开始下载任务的时候,View(Activity)中没有具体的实现,只是调用了Presenter中的download方法,而Presenter中的download又会去调用Model的download方法,Model又会在根据具体逻辑(在这里就是Http请求)的状态去调用Presenter中的方法,例如咱们在handleMessage方法中,调用mIDowndownPresenter.downloadProgress(percent)时,就会去调用Presenter的具体实现
@Override
public void downloadProgress(int progress) {
mIDownloadView.setProcessProgress(progress);
}复制代码
而他的内部实现又是操做具体的View,也就是咱们在Activity中初始化Presenter中传递的this,也就是当前Activity(View),这样最终回到了Activity中的
@Override
public void setProcessProgress(int progress) {
progressDialog.setProgress(progress);
}复制代码
咱们为progressDialog 设置进度。
至此,咱们就经过MVP 的模式实现了咱们以前所设想的Activity
这就是MVP !!!
经过上面的两种实现方案,相信每一个人都已经理解了MVC和MVP的区别;下面就其各自的优缺点再作一下
总结;固然,这里的优缺点只是相对而言。
上面两张图分别是MVC和MVP架构图。相信许多和我同样尝试去学习和了解MVP架构的同窗对这两图(或相似的图)并不陌生。
结构更加清晰*
咱们回过头再去看MVCActivity 的实现,暂且将咱们对Http请求的封装归结为Model(M),那么剩下的就只有Activity了,而这个Activity即实现视图逻辑,又须要实现部分业务逻辑,也就是说他既是Controller(C)又是View(V)。V和C的划分彻底不清晰;所以,传统的代码结构只能勉强称为MV 或者是MC,若是算上xml 的布局文件,才能牵强的称为MVC 结构。
而MVP 就不一样了,Model,View,Presenter各司其职,互相搭配,实现了解耦,彻底解放了Activity(或者是Fragment)。这就是MVP 的优点,代码结构更加清晰。能够这样说,同一个模块的实现,甚至容许几我的分工完成;假设有一个很是复杂的Activity,若是使用MVP 的模式开发;那么这个时候,定义好MVP的接口以后,就能够有人专门去作Model,另外一我的专门去作View;再由一我的写Presenter的代码,固然这须要极强的代码规范和协做能力;但这在传统的MVC模式中根本是没法想象的,全部的东西都在一个类里,两我的一块儿改,有了冲突怎么玩/(ㄒoㄒ)/~~。
需求变动,再也不是噩梦
假设如今有新的需求,产品经理认为下载失败后只有一个Toast提示太单调了(并且用户有可能错过了这Toast的显示,而误觉得APP失去了响应),所以,如今但愿在下载失败后弹出一个Dialog,能够重试下载任务。是想,若是代码使用传统的MVC 结构,恰巧这个代码不是你写的,或者说就是你写的,可是你已经忘记了具体的逻辑;那么为了实现这个需求你又得去从新捋一遍逻辑,到某个类的xxx行进行修改;可是若是使用MVP就不一样了View接口已经定义好了showFailToast就是用来显示错误提示的;所以即使代码不是你写的,你均可以很快的找到,应该去哪里改;而省去不少时间。
更容易写单元测试
这个就不展开说了,总之写过单元测试的人应该都有这样的体会。
MVP这么好,也不是没有缺点。
如图中所示,使用MVP 架构以后,多出了许多类;这是必然的;每个View(Activity或Fragment)都至少须要各自的Model、Presenter和View接口,在加上他们各自的实现,也就是说每个页面都会有6个java文件(算上Fragment或Activity,由于他就是View的实现),这样一个稍有点规模的APP,类就会变得异常的多,而每个类的加载又会消耗资源;所以,相较于MVC,这算是MVP最大的缺点了吧。
固然,对于这个问题咱们能够经过泛型参数、抽象父类的方式,将一些公用的Model及Presenter抽象出来。这应该就是使用MVP架构的精髓了。
我的感受,使用MVP 架构是利大于弊的;随着项目规模的增长,代码逻辑的清晰才是最重要的事情。何况Google官方也出推出了一系列关于MVP的使用demo。
所以,这也是官方提倡你们使用的。凡事,有利必有弊;类数目的增加是没法避免的事情,所以如何使用泛型和抽象优化MVP 的结构就变成了咱们用好
MVP的关键了。
固然,咱们不能为了MVP而去MVP,若是项目结构不是很庞大,业务不是很复杂;那么传统的MVC 架构足以,并且也方便!
年前的最后一个工做日了,我竟然写了一篇学习笔记;今天必定是上了假的班儿!明天回家过年,O(∩_∩)O哈哈哈~!每个人,新年快乐!