Android开发之MVP模式

时间:2021-01-19 21:12:19

MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。在MVC模式中,Activity应该属于view这一层,而在实际开发中,它既承担了view层,又包含了一些controller的东西。这对于开发与维护来说,都是不太友好的,耦合度太高。现在,把Activity中的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。

MVC模式

MVC模式的结构分为三部分:实体层的Mode,视图层的View,控制层的Controller。
Android开发之MVP模式

  • View层就是程序的UI界面,用于向用户展示数据以及接受用户的输入
  • Model层就是JavaBean实体类,用户保存用户实例数据
  • Controller控制器用于更新UI界面和数据实例

例如,View层接受用户的输入,然后通过Controller修改对应的model实例,同时,当Model实例的数据发生变化的时候,需要修改对应的UI界面,可以通过Controller达到更新界面的目的。当前,View层也可以直接更新Model数据,而不用通过Controller,这样对于一些简单的数据更新 小项目而言,会方便许多。

举个简单的例子,现在要实现一个飘雪的动态壁纸,可以给雪花定义一个实体类Snow,里面存在XY轴的坐标,View层就是SurfaceView或其它师徒,为了实现雪花飘的效果,可以启动一个后台线程,在线程里不同更新snow实例里的坐标值,这部分就是controller的工作了,controller里还要定时更新SurfaceView上面的雪花。更进一步的话,可以在SurfaceView上监听用户的点击,如果用户点击,只通过Controller对触摸点周围的Snow的坐标值进行调整,从而实现雪花在用户点击后弹开等效果。

MVP模式

在Android项目中,activity和fragment占据了大部分的开发工作,mvp模式就是专门为优化activity和fragment的代码而产生的。

按照MVC的分层,activity和fragment(后面只说activity)应该属于View层,用户展示UI界面,接收用户的输入以及生命周期相关的工作。以登录功能为例,如果把输入 输出以及具体实现登录操作的代码,都写在一个Activity中的话,那这个Activity代码将会显得非常臃肿,稍微复杂一些的页面,代码量分分钟就达到了上千行,并且这些功能不利于扩展复用,阅读和维护起来也是相当头疼的一件事。再让我们看一下MVP模式的结构图:
Android开发之MVP模式

MVP模式的核心思想就是把Activity中的UI逻辑抽象成view接口,把Controller相关的业务逻辑抽象成presenter接口,Model还是原来的Model。

MVP模式的作用

  • 分离了视图逻辑和业务逻辑,降低耦合度
  • Activity只处理生命周期相关的东西,代码更加简洁
  • 视图逻辑和业务逻辑分别抽象到了View和Presenter的接口中,提高了代码的可阅读性
  • Presenter被抽象成接口,可以有多种具体的实现,方便进行单元测试
  • 把业务逻辑抽象到Presenter中,避免后台线程引用Activity导致Activity的资源无法被系统回收从而引起内存泄漏和OOM

Activity代码变的更加简洁

相信很多人阅读代码的时候,都是从Activity开始的,对着一个1000+行代码的Activity,看了都觉得难受。

使用MVP之后,Activity就能瘦身许多了,基本上只有FindView、SetListener以及Init的代码。其他的就是对Presenter的调用,还有对View接口的实现。这种情形下阅读代码就容易多了,而且你只要看Presenter的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。

方便进行单元测试

一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格(习惯性上叫做MV模式,少了P),我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧……

MVP中,由于业务逻辑都在Presenter里,我们完全可以写一个PresenterTest的实现类继承Presenter的接口,现在只要在Activity里把Presenter的创建换成PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成PresenterTest吧。

避免 Activity 的内存泄露

Android APP 发生OOM的最大原因就是出现内存泄露造成APP的内存不够用,而造成内存泄露的两大原因之一就是Activity泄露(Activity Leak)(另一个原因是Bitmap泄露(Bitmap Leak))。

Java一个强大的功能就是其虚拟机的内存回收机制,这个功能使得Java用户在设计代码的时候,不用像C++用户那样考虑对象的回收问题。然而,Java用户总是喜欢随便写一大堆对象,然后幻想着虚拟机能帮他们处理好内存的回收工作。可是虚拟机在回收内存的时候,只会回收那些没有被引用的对象,被引用着的对象因为还可能会被调用,所以不能回收。

Activity是有生命周期的,用户随时可能切换Activity,当APP的内存不够用的时候,系统会回收处于后台的Activity的资源以避免OOM。

采用传统的MV模式,一大堆异步任务和对UI的操作都放在Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对Activity的引用。这样一来,即使Activity已经被切换到后台(onDestroy已经执行),这些异步任务仍然保留着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity Leak。Android的组件中,Activity对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收Activity对象,如果有Activity Leak,APP很容易因为内存不够而OOM。

采用MVP模式,只要在当前的Activity的onDestroy里,分离异步任务对Activity的引用,就能避免 Activity Leak。

MVP模式的使用

Android开发之MVP模式

上面一张简单的MVP模式的UML图,从图中可以看出,使用MVP,至少需要一下几步:
1. 创建IPresenter接口,把所有业务逻辑相关的接口都放在这里,并创建它的实现PresenterImpl
2. 创建IView接口,把所有视图逻辑的接口都放在这里,其实现类是当前Activity或Fragment
3. Activity里面包含一个IPresenter,而PresenterImpl里包含一个IView的引用,并包含Model。Activity只保留IPresenter的引用,其它工作都放在PresenterImpl中实现
4. Model不是必须的,依照实际情况,但一定会有View和Presenter的。

通过上面的介绍,MVP的主要特点就是把Activity里的许多逻辑都抽离到View和Presenter接口中去,并由具体的实现类来完成。这种写法多了许多IView和IPresenter的接口,在某种程度上加大了开发的工作量,刚开始使用MVP的小伙伴可能会觉得这种写法比较别扭,而且难以记住。其实一开始想太多也没有什么卵用,只要在具体项目中多写几次,就能熟悉MVP模式的写法,理解TA的意图,以及享受其带来的好处。

MVP模式简单示例

简单做几个计算器加的运算:

UI如下:
Android开发之MVP模式
项目代码结构图如下:
Android开发之MVP模式
包的命名和其它一样,可以按照模块进行命名,也可以直接按照顶层package进行明明。

首先,看一下MainActivity的代码:

public class MainActivity extends AppCompatActivity implements ICalcView, View.OnClickListener {

private EditText mNumber01;
private EditText mNumber02;
private TextView mResult;

private ICalcPrestenter mCalcPrestener;

private Button mBtnCalc;
private Button mBtnClear;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mNumber01 = (EditText) findViewById(R.id.et_number01);
mNumber02 = (EditText) findViewById(R.id.et_number02);
mResult = (TextView) findViewById(R.id.tv_result);

mBtnCalc = (Button) findViewById(R.id.btn_calc);
mBtnClear = (Button) findViewById(R.id.btn_clear);

mCalcPrestener = new CalcPresenter(this);

mBtnClear.setOnClickListener(this);
mBtnCalc.setOnClickListener(this);

}

@Override
public void clearText() {
mNumber01.setText("");
mNumber02.setText("");
mResult.setText("");
}

@Override
public void showResult(int result) {
mResult.setText(String.valueOf(result));
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_calc:
String number01 = mNumber01.getText().toString().trim();
if (TextUtils.isEmpty(number01)) {
showToast("第一个数字不能为空");
return;
}
String number02 = mNumber02.getText().toString().trim();
if (TextUtils.isEmpty(number02)) {
showToast("第二个数字不能为空");
return;
}
mCalcPrestener.sum(Integer.parseInt(number01), Integer.parseInt(number02));
break;
case R.id.btn_clear:
mCalcPrestener.clear();
break;
}
}

private void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}

如上,activity只持有一个presenter的引用,实现了IView接口,具体逻辑相关的操作交由presenter进行处理。

ICalcPrestenter代码如下:

public interface ICalcPrestenter {

void sum(int a,int b);

void clear();

}

其实现类CalcPrestenterImpl代码:

public class CalcPresenter implements ICalcPrestenter {

private ICalcView mCalcView;

public CalcPresenter(ICalcView calcView) {
mCalcView = calcView;
}

@Override
public void sum(int a, int b) {
mCalcView.showResult(a + b);
}

@Override
public void clear() {
mCalcView.clearText();
}
}

presenter实现类中,持有对IView(Activity实现了IView)的引用,逻辑处理完成后,通过调用IView的视图逻辑进行UI的更新。如果有其它Activity也需要用到相同的逻辑,可以直接进行复用,并且一个Activity中可以持有多个Presenter的引用,达到方便 耦合性低 阅读性和维护性高的目标,这才是MVP的核心思想。

好了,简单介绍这么多,本文内容文字参考了网络上部分文章,示例是根据自己的理解,做了一个超级简单的demo,如有错误之处,欢迎指出~