Android中的MVP模式讲解及实践

来源:互联网 发布:并发编程实战豆瓣 编辑:程序博客网 时间:2024/06/15 17:52

什么是MVP模式?

看NBA的都知道MVP(National Basketball Association Most Valuable Player Award ,简称MVP)这个概念,我当时的第一反应也是这个。但是,此MVP非彼MVP.我们今天要讨论的MVP其实同MVC一样,是一种编程模式和思想,也许更准确地讲是一种架构。

MVP和MVC

MVC简介

开发Android的都知道MVC。

  • M对应Model,代表业务数据
  • V对应View,代表视图
  • C对应Controller,代表控制器。

这里写图片描述

MVC架构将视图和数据分离,在WEB领域中应用的很广泛。 
用户通过界面组件进行操作,也就是View层,相应的动作会传递给控制器也就是Controller层,而Controller根据自己的业务逻辑去操作数据层也就是Model,而最终数据层的变化会同步更新到视图层。

MVC好处

这里直接引用百度百科

MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。 
MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。

可以看到MVC的主要目的是为了视图和数据分离,这对于开发大型软件来说更方便进行模块的划分,提高编码速度与质量。

Android中的MVC

Android世界中也经常运用到MVC模式。 
Activity对应视图界面也就是View层。 
数据库文件,Sharedprefrence,内存缓冲,磁盘缓冲等数据内容对应Model层。 
而Controller控制层基本上也由Activity层面来进行。

Android中mvc中基本动作流程

假设我们现在有这么一个需求,需要在一个界面上显示当天的天气,不仅如此,还可以通过列表项选择以往某一天的天气。 
mvc架构开发的话,大概是这样。

  1. 在layout制定相应的布局文件,然后显示在Activity上,用于显示天气信息。这对应于View层,这里的View并不是Android中开发中的组件view而是对视图的统称.
  2. Activity在onCreate方法或者onResume方法去服务器获取数据,或者通过界面上的某个按钮之类去启动获取服务器数据的任务,这里就对应到View—>Controller,只不过这里的View和Controller对是由Activity来完成。
  3. Controller获取到了数据之后,分别存在,内存、磁盘和数据库中,并且数据获取成功或者失败后,Activity界面需要同步更新状态。这由对应上面流程中的Controller—>Model 和Model—->View。

这里的流程还算清晰,也便于理解。

MVP为什么?

上面讲解了MVC的基础知识,大家可能觉得MVC挺好的啊?怎么还要整一个MVP。是的MVC是挺好的,但是它也有它的缺点,特别是针对Androi开发。

因为Android的特殊性,使得Activity对应了MVC中的V和C,同时担任两个角色,就有了类似“既当爹又当妈”的感觉,这显然就不符合软件设计原则的“单一职责”原则。但现实中是很多的APP代码中有这么的处境,特别是Androi原生的很多系统APK,某些Activity动则几千行代码。 
况且,随着项目的深入发展,很多逻辑很越来越复杂,Activity处理的东西也会越来越多,代码越来越臃肿。这样一来维护起来的代价就会越来越高,这是因为View的变化会引起Controller的很多变化,反之亦然。用一句大白话来说明就是–某一段代码的变动会引起很多其他相关联的代码的改动,而程序员都是懒惰的,所以会恨死这样的代码。

而MVP就是要减轻在Android中的这种困惑。 
MVP是基于MVC的,它的架构图如下: 
这里写图片描述

  • M(Model) 数据相关层
  • V(View) 视图层,如Activity上的布局
  • P(Presenter) 纽带层,用来连接Model与View.

MVP开发在Android中的基本流程 
1. View层定义View.interface,用来定义View的行为。一般由Activity或者是Fragment来实现这个接口,它定义了View视图的各种变化,如设置Textview,加载对话框,更新进度条等。 
2. Model层定义Modle.interface,这个是用来定义数据层发生变化时的通知接口,因为Model不能直接与View交互,所以它与Presenter交互,然后再通过Presenter间接达到与View的交互。 
3. Presenter翻译的意思是主持人,也就是主持场合,控制节奏的意思。在这时Presenter就负责具体的业务逻辑,请求数据,把数据送到Model,或者监听Model的数据变化,接受View层的动作,负责通过通知View层的视图变化。

如果跟MVC的架构图对比的话,可以发现它们有相似之处也有不同。

相似之处

模块划分的相似 
MVC由Model、View、Controller构成。 
MVP由Model、View、Presenter构成。

不同的地方

  1. MVP中Presenter取代了MVC中的Controller
  2. MVC中Model、View、Controller之间相互发生通信,而MVP中Model与Presenter相互通信,View与Presenter相互通信,而Model与View之间没有通信。

Android中MVP的好处?

就Android层面上来讲MVC架构虽然好,但不是最好,情况前面有讲过。用一句话概括就是“模块界限很模糊”。而MVP的出现实际上就是将MVC进行升级,对应Android开发中就是帮助Activity解压。 
MVC中Activity同时充当了V和C的角色,这就属于界限划分不清楚。而MVP则划分的很清楚,Activity只充当V的角色,业务逻辑控制交给了Presenter.

个人对MVP模式的理解

这一段是我自己的看法,也许不正确。 
我个人觉得MVP没有什么很神秘的,因为Android SDK上开发,本来就差不多是MVC的角色。Activity基本上Android开发中最重要的一环。 
我以前在团队工作的时候,团队分工是每人负责相应的Activity,在这里Activity是最小的开发单元。再后来,某些Activity变得越来越重要,越来越复杂,代码也越来越多,这样会造成团队某个人的开发任务重,而其他的团队成员也帮不上忙。而MVP的出现可以将Activity再细分,划为View和Presenter两个部分,所以Activity不再是最小的开发单元,如果可以完全可以这样分配任务,一个开发人员负责View部分,另一个开发人员负责Presenter部分。 
况且因为MVP的划分,所以各个部分其实相对独立,V的变动会对P的部分造成较少的影响,而M对V或者说V对M几乎是透明的。
因为Presenter的存在,View和Model就可以很轻松,顶多Presenter累一点。 
还有一个特点是MVP模式很适合测试,单独测试VIEW成了一种可能。我们可以模拟View和Model的数据来测试Presenter的逻辑。

MVP实战

在现在的公司项目中,我已经用上了MVP模式开发。但是在这里,我不想照搬代码。主要是因为怕复杂的代码或者其它的知识点干扰MVP本身的脉络。所以,我用一个简单的DEMO来讲解,大家一看就明白。

场景需求

假设现在需要做一款APP,就是显示天气,界面很简单,一个TextView显示天气信息,一个Button用来请求实时天气。 
如下图所示 
这里写图片描述

软件启动后,会自动获取天气,然后TextView就可以显示信息。而用户点击获取实时天气的按钮,界面上会弹出正在获取中的进度对话框,等待数据加载成功后,对话框消失。Textview显示就新的天气情况。 
这里写图片描述

代码开发

因为选定MVP模式,所以第一步就是包的组织。 
这里写图片描述

View层的接口定义及实现

在MVP中Activity用来专注视图的表现。 
而在本例子中View的表现有哪些呢?很多教程直接就上来贴代码,个人觉得这样是不好的。View的表现当然要用View.interface接口来定义 
现在我们来分析一下,在本例中View应该有哪些表现。

1.显示天气信息

那好,接口方法可以这样定义。

public void onInfoUpdate(String info);
  • 1
2.显示获取信息等待对话框

接口可以这样写

public void showWaitingDialog();
  • 1
3.取消显示对话框
public void dissmissWaitingDialog();
  • 1

最终View.interface就完成了,非常简单

public interface IWetherView {    public void onInfoUpdate(String info);    public void showWaitingDialog();    public void dissmissWaitingDialog();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

接口文件已经定义好了,那么View的实现呢?在这里用MainActivity去实现它。

public class MainActivity extends AppCompatActivity implements IWetherView{    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    @Override    public void onInfoUpdate(String info) {    }    @Override    public void showWaitingDialog() {    }    @Override    public void dissmissWaitingDialog() {    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

具体的业务代码,我们等会再实现。

Model层的接口定义及实现

Model层是数据层,用来存储数据并且提供数据。在这里为了便于演示,数据被简化为了String类型。 
接口定义如下:

public interface IWetherModel {    //提供数据    public String getInfo();    //存储数据    public void setInfo(String info);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

它的实现文件如下:

public class IWetherImpl implements IWetherModel {    @Override    public String getInfo() {        return null;    }    @Override    public void setInfo(String info) {    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Presenter代码及实现

Presenter是个大忙人,因为要同时对View和Model对接,所以内部必须持有它们的接口引用。 
所以有如下:

public class WetherPresenter {    IWetherModel mModel;    IWetherView mView;}
  • 1
  • 2
  • 3
  • 4
Presenter与View的通信
View—–>Presenter

从视图界面出发,用户要请求数据,而Presenter是具体实现者,所以Presenter要提供方法代View的实现者调用,并且View的实现中必须要有Presenter的引用。 
所以MainActivity.java中要有WetherPresenter的引用。

public class MainActivity extends AppCompatActivity implements IWetherView{    ......    WetherPresenter mPresenter;    ......}    
  • 1
  • 2
  • 3
  • 4
  • 5

而Presenter也要开发API供View调用。 
所以Presenter要有requestWetherInfo()方法:

public class WetherPresenter {    IWetherModel mModel;    IWetherView mView;    //供View层调用,用来请求天气数据    public void requestWetherInfo(){    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
presenter—–>View

presenter操作View,是通过View.interface,也就是View层定义的接口。 
所以很容易得到下面的代码:

public class WetherPresenter {    ......    private void showWaitingDialog(){        if (mView != null) {            mView.showWaitingDialog();        }    }    private void dissmissWaitingDialog(){        if (mView != null) {            mView.dissmissWaitingDialog();        }    }    private void updateWetherInfo(String info){        if (mView != null) {            mView.onInfoUpdate(info);        }    }    ......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

因为Presenter持有View的引用,所以在这里要将View.interface注入到Presenter当中。

public class WetherPresenter {    IWetherModel mModel;    IWetherView mView;    ......    public WetherPresenter(IWetherView mView) {        this.mView = mView;    }    ......}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
Presenter与Model的通信

Presenter与Model的通信也是双方的。

Presenter—->Model

presenter获取到了数据,可以交给Model处理

private void saveInfo(String info){        mModel.setInfo(info);    }
  • 1
  • 2
  • 3
Model—–>Presenter

Model处理完数据后它也能对Presenter提供数据。Presenter可以通过Model对象获取本地数据。

WetherPresenter.java

private String localInfo(){        return mModel.getInfo();}
  • 1
  • 2
  • 3
Presenter代码实现

前面已经讲了Presenter与Model,Presenter与View之间的通信,现在就可以编写代码将它们粘合起来。 
Presenter本身需要向服务器获取代码,所以还要编写它的相应方法:

public void requestWetherInfo(){        getNetworkInfo();;    }private void getNetworkInfo(){        new Thread(new Runnable() {            @Override            public void run() {                try {                    //打开等待对话框                    showWaitingDialog();                    //模拟网络耗时                    Thread.sleep(6000);                    String info = "21度,晴转多云";                    //保存到Model层                    saveInfo(info);                    //从Model层获取数据,为了演示效果,实际开发中根据情况需要。                    String localinfo = localInfo();                    //通知View层改变视图                    updateWetherInfo(localinfo);                } catch (InterruptedException e) {                    e.printStackTrace();                }finally {                    //取消对话框                    dissmissWaitingDialog();                }            }        }).start();    }
  • 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

到此,完整的Presenter代码如下:

public class WetherPresenter {    IWetherModel mModel;    IWetherView mView;    public WetherPresenter(IWetherView mView) {        this.mView = mView;        mModel = new IWetherModelImpl();    }    public void requestWetherInfo(){        getNetworkInfo();;    }    private void showWaitingDialog(){        if (mView != null) {            mView.showWaitingDialog();        }    }    private void dissmissWaitingDialog(){        if (mView != null) {            mView.dissmissWaitingDialog();        }    }    private void updateWetherInfo(String info){        if (mView != null) {            mView.onInfoUpdate(info);        }    }    private void saveInfo(String info){        mModel.setInfo(info);    }    private String localInfo(){        return mModel.getInfo();    }    private void getNetworkInfo(){        new Thread(new Runnable() {            @Override            public void run() {                try {                    //打开等待对话框                    showWaitingDialog();                    //模拟网络耗时                    Thread.sleep(6000);                    String info = "21度,晴转多云";                    //保存到Model层                    saveInfo(info);                    //从Model层获取数据,为了演示效果,实际开发中根据情况需要。                    String localinfo = localInfo();                    //通知View层改变视图                    updateWetherInfo(localinfo);                } catch (InterruptedException e) {                    e.printStackTrace();                }finally {                    //取消对话框                    dissmissWaitingDialog();                }            }        }).start();    }}
  • 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

MainActivity代码编写

  1. 生成Presenter。这个在Activity中的onCreate方法中,并把自身当成IWetherView注入到presenter当中。
mPresenter = new WetherPresenter(this);
  • 1

2 . 操作Presenter。当用户点击按钮时,通过调用mPresenter获取数据,然后静待更新。

mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mPresenter.requestWetherInfo();            } });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. View.interface回调方法被触发时,进行相应的视图更新。 
    这里主要的视图有 
    • 显示对话框
    • 取消对话框
    • 显示 天气信息。

对应代码如下:

@Override    public void onInfoUpdate(final String info) {        Log.d(TAG, "onInfoUpdate: "+info);        runOnUiThread(new Runnable() {            @Override            public void run() {                mTvInfo.setText(info);            }        });    }    @Override    public void showWaitingDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                if(mDialog != null && mDialog.isShowing()){                    mDialog.dismiss();                }                mDialog = ProgressDialog.show(MainActivity.this,"","正在获取中...");            }        });    }    @Override    public void dissmissWaitingDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                if(mDialog != null && mDialog.isShowing()){                    mDialog.dismiss();                }            }        });    }
  • 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

所以整个MainActivity.java代码如下:

public class MainActivity extends AppCompatActivity implements IWetherView{    private static final String TAG = "MainActivity";    WetherPresenter mPresenter;    private TextView mTvInfo;    private Button mButton;    private ProgressDialog mDialog;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mPresenter = new WetherPresenter(this);        mTvInfo = (TextView) findViewById(R.id.tv_info);        mButton = (Button) findViewById(R.id.btn_request);        mButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                mPresenter.requestWetherInfo();            }        });    }    @Override    public void onInfoUpdate(final String info) {        Log.d(TAG, "onInfoUpdate: "+info);        runOnUiThread(new Runnable() {            @Override            public void run() {                mTvInfo.setText(info);            }        });    }    @Override    public void showWaitingDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                if(mDialog != null && mDialog.isShowing()){                    mDialog.dismiss();                }                mDialog = ProgressDialog.show(MainActivity.this,"","正在获取中...");            }        });    }    @Override    public void dissmissWaitingDialog() {        runOnUiThread(new Runnable() {            @Override            public void run() {                if(mDialog != null && mDialog.isShowing()){                    mDialog.dismiss();                }            }        });    }}
  • 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

效果如上面的: 
这里写图片描述

总结

mvp非常适合大型的APP开发,越复杂它的优势越明显,但是如果APP代码本身很简明,mvp就有点绕弯子的感觉了。

原创粉丝点击