MVP模式
MVP模式在Android中的应用已经越来越广了,总的来说,它可以Activity 代码变得更加简洁,方便进行单元测试,也可以避免 Activity 的内存泄露
按照MVC的分层,Activity和Fragment(后面只说Activity)应该属于View层,用于展示UI界面,以及接收用户的输入,此外还要承担一些生命周期的工作。Activity是在Android开发中充当非常重要的角色,特别是TA的生命周期的功能,所以开发的时候我们经常把一些业务逻辑直接写在Activity里面,这非常直观方便,代价就是Activity会越来越臃肿,超过1000行代码是常有的事,而且如果是一些可以通用的业务逻辑(比如用户登录),写在具体的Activity里就意味着这个逻辑不能复用了。如果有进行代码重构经验的人,看到1000+行的类肯定会有所顾虑。因此,Activity不仅承担了View的角色,还承担了一部分的Controller角色,这样一来V和C就耦合在一起了,虽然这样写方便,但是如果业务调整的话,要维护起来就难了,而且在一个臃肿的Activity类查找业务逻辑的代码也会非常蛋疼,所以看起来有必要在Activity中,把View和Controller抽离开来,而这就是MVP模式的工作了。
MVP模式的核心思想:
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
这就是MVP模式,现在这样的话,Activity的工作的简单了,只用来响应生命周期,其他工作都丢到Presenter中去完成。从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。
传统MVP模式的弊端
普通MVP主要存在一下问题:
View和Model无法避免存在一定的耦合。
View难以复用
在Activity中很难避免一些数据的处理操作,比如用OnSaveInstanceState()去保存状态,用OnRestoreInstanceState()去恢复状态。
TheMVP原理介绍
既然知道了这些问题,我们的解决办法自然是不要将Activity作为View层而去单独包含Presenter类进来。反过来,我们将Activity(Fragment)作为Presenter层的代码,包含一个View层的类来。
使用Activity作为Presenter的优点就在于,可以原封不动的使用Activity本身的生命周期去处理项目逻辑,而不需要强加给另一个包含类,甚至记忆额外自定义的生命周期。
而同时作为独立的View层,我们的视图可以原封不动的传递给Presenter(不管是Activity或者Fragment),而不需要改任何代码。对于一个开发团队,完全可以将View层的东西交给一个人编写,而将业务实现交给另一个人编写。而随着逻辑变化对View的更改,只需要通过Presenter层的包含一个代理对象————ViewDelegate来操作相应的更改方法就够了。
与传统androidMVP不同(原因上文已经说了),TheMVP使用Activity作为Presenter层来处理代码逻辑,通过让Activity包含一个ViewDelegate对象来间接操作View层对外提供的方法,从而做到完全解耦视图层。如下图:
优化方式
TheMVP最大的特点在于关注View的复用性,解耦View与业务逻辑之间的关系。相应的,Presenter的复用性有较大欠缺。
为了解决Presenter的复用性(准确的说,应该是拆分粒度)问题,可以对TheMVP进行了扩展。
TheMVP 定制
将多个无activity的presenter挂载到以activity为载体的presenter中,同步activity的生命周期到无activity的presenter中。通过include或者ViewStub的方式在挂载layout到主activity presenter的layout中。
这意味着项目中存在两种p-v关系,有activity和无activity的(无activity的称为component presenter)。
结合DataBinding
一个好的架构一定是对扩展开放,对修改关闭的,这是软件设计模式的开闭原则。
如果你之前有了解过Google的DataBinding,你一定知道ViewModel的概念。DataBinding 解决了 Android UI 编程中的一个痛点,就是要给一个控件设置内容,必须首先获取到控件的对象,并调用set方法(例如setText()),传一个数据进去。
DataBinding允许你使用这样的代码为控件设置内容。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
其中user.lastName表示在项目中的数据类的user对象的lastName属性。
DataBinding存在的问题
另一方面,虽然databinding虽然简化了试图与数据绑定的代码,但同时也牺牲了布局文件的可复用性。例如我们经常会遇到的,一个Fragment与另一个Fragment,在布局上完全一样,而仅仅是数据不同,这时我们通常会用代码去控制在不同的界面显示不同的数据。然而,如果将数据写死在xml中,就失去了布局的复用性。
因此,我们可以尝试将视图与数据模型绑定的逻辑抽出,单独建立一个类来使用代码控制,这样如果发生相同的界面复用,只需要重写视图与数据绑定的逻辑就够了,其他的代码仍然不变。
定义一个ViewModel层的接口,其中包含两个泛型分别为View层的代理和Model层的代理:
public interface DataBinder<T extends IDelegate, D extends IModel> {
void viewBindModel(T viewDelegate, D data);
}
为Presenter添加扩展,使它能够支持DataBinder。其中使用getDataBinder()方法,得到开发中具体的某个界面的ViewModel层的扩展,然后我们只需要在数据改变的时候,手动调用notifyModelChanged()方法,即可使ViewModel中定义的绑定逻辑生效
进一步简化
之前的改进中,我们都需要通过一个databind对象来更改view界面,但在目前的客户sdk中,使用到的databind的数据比较少,实际上我们可以直接在delegate中对view进行更改,因此只需在delegate中提供一个对外开放的接口即可。
public void setText(String text) {
mEditText.setText(text);
}