Android MVP 详解(上)

MVP 在 Android 上的使用其实已经有挺长一段时间了,长到似乎有点“过时”了(目前风头正劲的是MVVM),那为什么现在还要讲 MVP。今天我想要讨论它的主要原因有如下几点:

2014年年底偶然得知在Android开发中出现了MVP这种模式,当时觉得这东西挺好,正好赶上公司要做一个新的小项目,于是尝试了一下。仿照网上的Demo分出View、Model、Presenter层,抽取View接口,看起来像那么回事的用MVP完成了整个项目。因为项目简单,期间也没有遇到什么坑,但是总觉得还有那些地方不对。当时网上一些关于Android MVP的介绍都有点浅尝辄止,一个登录或者根据地区查询天气等的小Demo,没有实际在项目中应用的示例,所以在用MVP做完一个小项目之后还是不敢在主项目中轻易尝试。首先,主项目比较混乱,改动起来工作量很大,而工期经常较紧,时间不允许;其次,知道自身对MVP理解还不够,怕掉坑里去;最后,也是最重要的一点,当时的项目不是按功能模块划分的包结构,如果改为MVP那是真的就回不到过去了。好了,废话不多说,今天主要是想分享一下,本人对MVP的浅见,以及如何使用MVP模式搭建一个项目框架。纯属一家之言,不足之处,请见谅。

MVC(Model-View-Controller,模型-视图-控制器)模式是80年代Smalltalk-80出现的一种软件设计模式,后来得到了广泛的应用,其主要目的在于促进应用中模型,视图,控制器间的关注的清晰分离。MVP(Model-View-Presenter,模型-视图-表示器)模式则是由IBM开发出来的一个针对C++和Java的编程模型,大概出现于2000年,是MVC模式的一个变种,主要用来隔离UI、UI逻辑和业务逻辑、数据。也就是说,MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

顾名思义,PV(Passive View)是一个被动的View,针对包含其中的UI元素(比如控件)的操作不是由View自身来操作,而交给Presenter来操控。

在SoC(Supervising Controller)模式下,为了降低Presenter的复杂度,将诸如数据绑定和格式化这样简单的UI处理逻辑逻辑转移到View中,这些处理逻辑会体现在View实现的接口中。

任何一种思想的产生都有其特定的背景,在软件开发中也是如此。在软件复杂度增长,需求不断变更的客观条件下,为了更好的解决这些问题,出现了各种软件架构思想、编程思想以及设计模式。(因为人的能力并没有“跟上”机器,所以才会出现各种模式、方法、工具等等来补足人的不足,以最大地透支机器性能。–Indream Luo)

相信做过客户端(PC、Android、iOS等)或者前端开发的童鞋都听过MVC、MVP、MVVM这些名词(就算不了解也大致知道有这个东西吧),这些都是为了解决拥有图像界面的程序开发复杂性而产生的模式。这里说的是模式,当然有各种各样的框架方便开发者在项目中应用这种模式,这不是本文重点。

有前辈(Indream Luo)说过,架构是对客观不足的妥协,规范是对主观不足的妥协。对此我深表赞同,先不管这些,我们来看看GUI是怎么和MVX扯上关系的。

先搞清楚一个顺序,是GUI应用程序的出现导致了MVC的产生。GUI应用程序提供给用户可视化的操作界面,这个界面提供给用户数据和信息。在PC上用户与界面的交互主要依赖(键盘,鼠标等。这些操作会执行一些应用逻辑,应用逻辑(application logic)可能会触发一定的业务逻辑(business logic)使应用程序数据的发生变更,数据的变更自然需要用户界面的同步变更以提供最准确的信息。在开发这类应用程序时,为更好的管理应用程序的复杂性,基于职责分离(Speration of Duties)的思想都会对应用程序进行分层。在开发GUI应用程序的时候,会把管理用户界面的层次称为View,应用程序的数据为Model(注意这里的Model指的是Domain Model,这个应用程序对需要解决的问题的数据抽象,不包含应用的状态,可以简单理解为对象)。Model提供数据操作的接口,执行相应的业务逻辑。有了View和Model的分层,那么问题就来了:View如何同步Model的变更,View和Model之间如何粘合在一起。(所谓的MVX中的X都可以归纳为对这个问题不同的处理方式)(引自:戴嘉华)。

早在上个世纪70年代,美国的施乐公司(Xerox)的工程师研发了Smalltalk编程语言,并且开始用它编写图形界面的应用程序。而在Smalltalk-80这个版本的时候,一位叫Trygve Reenskaug的工程师设计了MVC图形应用程序的架构模式,极大地降低了图形应用程序的管理难度。而在(GoF)的设计模式当中并没有把MVC当做是设计模式,而仅仅是把它看成解决问题的一些类的集合。Smalltalk-80 MVC和GoF描述的MVC是最经典的MVC模式。

看到这服务端的童鞋有话要说:我们也用MVC,你看Structs、SpringMVC这些都是经典的MVC框架。那服务端的MVC和GUI开发中的MVC有何不同之处了,请看下面的分析。

在Web服务端开发的时候也会接触到MVC模式,而这种MVC模式不能严格称为MVC模式。经典的MVC模式只是解决客户端图形界面应用程序的问题,而对服务端无效。服务端的MVC模式又自己特定的名字:MVC Model 2,或者叫JSP Model 2,或者直接就是Model 2 。

好吧,说了等于没说,总之一句话,我们今天所说的MVX都有一个前提,那就是得有GUI,得是来解决GUI应用程序开发中遇到的问题的。所以,我们只要关心最经典的MVC就可以了,想了解更多的请自行Google。

MVP模式是MVC模式的改良。在上个世纪90年代,IBM旗下的子公司Taligent在用C/C++开发一个叫CommonPoint的图形界面应用系统的时候提出来的。

MVVM模式最早是微软公司提出,并且了大量使用在WPF和Sliverlight中。2005年微软工程师John Gossman在自己的博客上首次公布了MVVM模式。

(以下内容参考自:MVP在Android平台上的应用,原文作者konmik,译者MiJack)

当你在应用中只使用Model-View时,到最后,你会发现“所有的事物都被连接到一起”。

如果这张图看上去还不是很复杂,那么请你想象一下以下情况:每一个View在任意一个时刻都有可能出现或者消失。不要忘记View的保存和恢复,在临时的view上挂载一个后台任务。

“所有的事物都被连接到一起”的替代品是一个万能对象(god object)。

god object是十分复杂的,他的每一个部分都不能重复利用,无法轻易的测试、或者调试和重构。

复杂的任务被分成细小的任务,并且很容易解决。越小的东西,bug越少,越容易debug,更好测试。在MVP模式下的View层将会变得简单,所以即便是他请求数据的时候也不需要回调函数。View逻辑变成十分直接。

当你编写一个Actviity、Fragment、自定义View的时候,你会把所有的和后台任务相关的方法写在一个静态类或者外部类中。这样,你的Task不再和Activity联系在一起,这既不会导致内存泄露,也不依赖于Activity的重建。

(以下内容参考自:MVP在Android平台上的应用,原文作者konmik,译者MiJack)

情景 1: 当用户切换屏幕、更改语言设置或者链接外部的模拟器时,往往意味着设置改变。 相关更多请阅读这里。

情景 2:Activity的重启发生在当用户在开发者选项中选中了“Don’t keep activities”(“中文下为 不保留活动”)的复选框,然后另一个Activity在最顶上的时候。

情景 3: 进程的重启发生在应用运行在后台,但是这个时候内存不够的情况下。

第一个部分,用Android的API可以实现。第二个部分,就是Presenter的作用了。Presenter将会记住有哪些请求需要执行,当进程在执行过程中重启时,Presenter将会出现执行它们。

1. 降低耦合度,实现了Model和View真正的完全分离,可以修改View而不影响Modle

4. Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑(当然是在View的改动不影响业务逻辑的前提下)

5. 利于测试驱动开发。以前的Android开发是难以进行单元测试的(虽然很多Android开发者都没有写过测试用例,但是随着项目变得越来越复杂,没有测试是很难保证软件质量的;而且近几年来Android上的测试框架已经有了长足的发展——开始写测试用例吧),在使用MVP的项目中Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。

6. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。

2. 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。

3. 如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。

模型:表示数据模型和业务逻辑(business logic)。模型并不总是DataSet,DataTable之类的东西,它代表着一类组件(components)或类(class),这些组件或类可以向外部提供数据,同时也能从外部获取数据并将这些数据存储在某个地方。简单的理解,可以把模型想象成“外观类(facade class)”。译注:这里的外观是指“外观模式”中所说的外观。外观的一般作用是为一个复杂的子系统提供高层次的简单易用的访问接口,可以参看下面的图来理解它的原理:

视图:将数据呈现给用户。一般的视图都只是包含用户界面(UI),而不包含界面逻辑。比如,Asp.net中包含控件的页面(page)就是一个视图。视图可以从模型中读取数据,但是不能修改或更新模型。

控制器:View捕获到用户交互操作后会直接转发给Controller,后者完成相应的UI逻辑。如果需要涉及业务功能的调用,Controller会直接调用Model。在完成UI处理之后,Controller会根据需要控制原View或者创建新的View对用户交互操作予以响应。

层现器:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。Presenter包含了根据用户在视图中的行为去更新模型的逻辑。视图仅仅只是将用户的行为告知Presenter,而Presenter负责从视图中取得数据然后发送给模型。

视图模型:binder 所在之处,是 View 的抽象,对外暴露出公共属性和命令,它是View的抽象,负责View与Model之间信息转换,将View的Command传送到Model。ViewModel的含义就是 Model of View,视图的模型。它的含义包含了领域模型(Domain Model)和视图的状态(State)。可以简单把ViewModel理解为页面上所显示内容的数据抽象,和Domain Model不一样,ViewModel更适合用来描述View。

MVC模式、MVP模式和MVVM模式都作为用来分离UI层与业务层的一种开发模式。这些模式之间的差异可以归纳为对这个问题处理的方式的不同。

相信不少童鞋和我有过同样的疑惑:MVX分为了M-V-X三层,那这到底和软件的三层架构有何关系呢?我们带着问题继续往下阅读。

三层架构是一个分层式的软件体系架构设计,它可适用于任何一个项目。通常意义上的三层架构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。(参考自:百度百科)

首先,我想说三层架构(分层架构)和 MVX 没有什么关系,它们不在同一个层次上(三层是一种架构思想,更多的是和事件驱动架构、微内核架构等放在一起讨论,而我更喜欢把 MVX 做为模式来对待)。

三层是从整个应用程序架构的角度来分为DAL(数据访问层)、BLL(业务逻辑层)、WEB层(界面层)各司其职,意在职责分离;三层是为了解决整个应用程序中各个业务操作过程中不同阶段的代码封装的问题,为了使程序员更加专注的处理某阶段的业务逻辑;并且三层只是多层架构中的一种情况,完全可以根据需要分为多层。

MVC 主要是为了解决应用程序用户界面的样式替换问题,把展示数据的 HTML 页面尽可能的和业务代码分离。MVC把纯净的界面展示逻辑(用户界面)独立到一些文件中(Views),把一些和用户交互的程序逻辑(Controller)单独放在一些文件中,在 Views 和 Controller 中传递数据使用一些专门封装数据的实体对象,这些对象,统称为Models。而在其后出现的 MVP 以及 MVVM 与 MVC 的作用类似,MVX 主要的区别在于如何解决 M 与 V 之间的连接与更新。

总之一句话,MVX 是一种模式,Spring MVC 以及 ASP.NET MVC 等是一个基于MVC模式的开发框架,三层架构是一种架构。

其次,它们都有一个表现层,但是这两者的展现层并不是一样的。可以这样看待 MVX 与三层架构中的表现层的关系,MVX 中的 V 和 X 都属于三层架构中的表现层,可以看下图的示意。

最后,虽然都有提到 Model,但是在 MVX 中没有把业务的逻辑访问看成两个层,这是采用三层架构或 MVX 搭建程序最主要的区别。在三层架构中Model 的概念与 MVX 中 Model 的概念是不一样的,“三层”中典型的Model 层是以实体类构成的,而MVC里,则是由业务逻辑与访问数据组成的。

也就是说,MVX 与三层架构说的根本不是一回事。在所谓的“三层”中,它要求你将BLL层独立出来,它只是告诉你表示层和业务逻辑层之间的静态关系。而 MVX 则告诉你在这个具体的地方如何处理其动态驱动流程,尽管 MVC 仍然粗糙(甚至 MVP、MVVM也是粗糙的),但是已经比所谓三层更细致一些了(三层是架构好吗…)。

絮絮叨叨说了一大堆,终于干货要来了。正所谓:Talk is cheap,show me the code.下面会给出示例代码,请继续阅读。

下面,我会尝试一一细数 Android 上常见的 MVP 实现方式(说明:并没有什么排序规则,只谈大家的实现思路,展示的顺序只是为了方便大家的理解和阅读)。

这其实是我最先接触 MVP 时看到的示例,代码很少,但是把 MVP 的分层展示的挺清晰。

现在的 Andorid 开发怎么能够离开网络,来一个有网络的示例。该天气查询 Demo,是通过访问 Web 服务获取地区的天气信息(返回为JSON),然后在 Activity 中用 TextView 展示出来。

上面的示例 View 都是 Activity 来承担的,Presenter 是一个普通的类,前面讨论过 Android 在不同场景下会进入不同的生命周期,这将可能导致 Presenter 也随着其生命周期需要做出响应。从这个角度考虑,有不少开发者提出了 MVP 实现的其他思路,接下来我们要探讨的就是使用 Activity/Fragment 作为 Presenter 的一些实现方案。

TheMVP使用Activity作为Presenter层来处理代码逻辑,通过让Activity包含一个ViewDelegate对象来间接操作View层对外提供的方法,从而做到完全解耦视图层。

我给这篇关于Android库的博客起的名字灵感来源于《老爸老妈浪漫史》中的建筑设计师Ted Mosby。这个Mosby库可以帮助大家在Android上通过Model-View-Presenter模式做出一个完善稳健、可重复使用的软件,还可以借助ViewState轻松实现屏幕翻转。

这又是一种解决Activity/Fragment生命周期在屏幕翻转等场景下对Presenter的处理的思路。

就像刚才说的一样,关键问题就是在哪里存储Presenter以及什么时候销毁它们。而我们刚刚就看到了Loader的强大之处:由安卓系统框架提供,有单独生命周期,会被自动回收且不必在后台运行。

所以思考一下需求以及Loader的功能,我们可以让Loader作为Presenter的提供者,而不需要担心手机状态改变。

这里的重点就在于同步使用Loader时,我们可以知道在生命周期的哪个阶段Presenter被创建了并且可以工作了。甚至是在Activity/Fragment可见之前。

使用Loader,思路很有新意,关键确实解决了问题,更关键的是使用的是 Android Framework 提供的功能。

大 Boss 总是最后出场,对于 Android 上 MVP 的实现,Google 给也出了一些建议和实例,赶紧看看去吧。

ChrisBannes的开源项目Philm,其整体架构是一套MVP的实现。这里有一篇分析该项目的文章,可以直接去读源码,也可以先看看lightSky是怎么分析的。

干货集中营有不少的开源实现都是 MVP 模式的(下面的 App 是官网上列出来的,具体是否都采用了 MVP 本人没有一一查阅)。

上述众多解决方案都集中在 Presenter 实现的问题上,这主要是由于 Activity、Fragment 的复杂性导致的,它们有众多生命周期,它们无所不能,是否把它们仅仅视作 View 成了争论的焦点。个人认为从编码的难易程度和编码的习惯来说,我赞成把 Activity、Fragment 作为 View 即可,我们可以考虑其他方式来保证Presenter的生命周期和防止 Presenter 引起内存泄漏。其中使用 Loader 的方案就非常优雅,下面在本人的示例项目中也会采用这种方式。

发表评论

电子邮件地址不会被公开。 必填项已用*标注