【51CTO.com原创稿件】苏宁+App 是苏宁易购集团零售云研发中心主要产品之一,由于项目处于初期阶段,业务逻辑复杂,导致业务需求变动快,常常在开发甚至测试过程中出现界面或者后台接口调整的情况。
App 客户端如何在外部需求不断变化的情况下,降低模块耦合,尽可能减少每次代码修改量,一方面减少开发人员的工作量,另一方面降低测试工程师的工作量,最终顺利完成项目迭代开发。
为什么使用 MVP 模式
相信在 2014 年之前,绝大部分人开发 Android 应用,都是使用的 MVC 模式。
M 跟 V 一般没有什么问题,Controller 层也就是对应 Activity 类,它的首要职责是加载应用的布局和初始化用户界面,并接受和处理来自用户的操作请求,进而作出响应。
随着界面及其逻辑的复杂度不断提升,Activity 类的职责不断增加,以致变得庞大臃肿,打开以前项目的 Activity,超过 2000 行的不在少数。
另外,由于项目的特殊性,互联网产品讲究速度,尤其是新产品,上线的时间会决定你在市场上的占有量。
App 的理想情况是 UED 做好视觉稿,后台接口准备完毕,客户端同学一边做页面一边调试接口,做完自测后顺利交付测试。但是,理想很丰满,现实很骨感。
实际情况是,我们开始 Coding 的时候,只有一份接口文档跟交互图。我们需要思考的是,我们必须把界面和接口数据解耦,接口联调和测试工作不能依赖界面的完成,当完成业务层代码后,就可以测试业务功能。
基于上面的背景,我们选择了 MVP 模式。
什么是 MVP 模式
我们上面说的 MVP 架构,是 Google 开源的一个设计模式,它主要是为了细分视图(View)与模型(Model)的功能,让 View 只做两件事:
- 完成用户的交互。
- 显示界面布局,同时让 Model 做数据的处理,业务逻辑放到另外的一个类(Presenter)中。
下面做具体分析:
- M 即 M 层,在项目中负责数据的处理,包括本地数据库查询,网络数据获取都在这一层中完成。
- View 即 V 层,在项目中是 UI 模块,也就是各种 activity/fragment,负责绘制 UI 元素、与用户进行交互。
- P 即 P 层,在项目中做为 View 与 Model 的桥梁,M 跟 V 层不直接交互,M 层在获取到数据之后,传递到 P,P 层再通过接口回调到 View 层,同样,View 层的点击等事件,通过 P 层去通知 M 层去处理。
如下图所示:
MVP 模式应用实战
苏宁+App 项目结构
苏宁+App 项目结构图如下:
目前 App 整体项目架构如图中所示,各个层次的介绍如下:
- 前端界面层:界面相关布局,如各种 activity/fragment 类。
- 业务逻辑层:业务逻辑相关,如各种 Presenter 类。
- 数据层:数据相关,包括数据存储,获取,如各种 Model 类。
- 运行服务层:伴随应用生命周期自动初始化,自动销毁,提供一系列服务给其他业务模块调用,如各种 Service 类。
- 业务框架层:针对当前 App 跟业务有耦合度的公共方法,组件抽取。
- 基础框架层:跟业务无关的底层组件,可以给多个 App 同时使用。
- 系统层:Android 系统底层。
通过上面的架构图可以很直观的看出,我们日常业务功能迭代的时候,主要修改或者新增的代码都在前面三层,这里主要讲前面三层的使用规范。
目录结构
下图为使用 MVP 模式时,购物车确认订单页面的目录结构:
- model—数据处理。
- presenter—业务处理。
- task—网络请求。
- ui—页面。
- util—当前模块公共类。
- view—页面刷新回调接口。
总体逻辑设计
如下图,为购物车 2 界面,下面将围绕该界面来讲解如何用 MVP 实现具体业务功能。
为了更加直观看到 MVP 在当前业务中的使用,我们画了类图跟时序图,通过类图我们可以清楚类的设计,如下所示:
通过下面的时序图,我们可以很清楚的看到调用关系:
通过上面两张图,我们可以看到 MVP 在当前业务中对应的角色以及调用关系,下面深入代码层面继续讲解。
代码实现
M 层(model)
项目中很多网络请求是重复的,比如很多页面都会用到店铺信息接口,如果每个页面都要在不同 Model 写一遍,那么复用性很弱。
所以跟 Google 在 Github 发布的 MVPDemo 不同,我们项目中每个网络接口都单独写成一个 Task,以确认订单页面为例:
- Model 层定义模型抽象类(PSCShopCart2DataSource)。
- 然后具体实现类(PSCShopCart2Repository)里面调用 Task,发送网络请求。
代码如下:
IView
MVP 模式中,M 层跟 V 层不能直接通信,数据是通过 Presenter 层接口回调到 V 层中。一般情况下,IView 里面的接口就对应 V 层的功能。
这边会有人觉得特别复杂的场景会出现很多接口的情况,当然如果真出现这种情况,该合并的接口还是要合并,到 Activity 中做简单的处理也是可以的。
实际开发中一定不能被框架限制,不管什么模式都是为了业务正常迭代。
代码如下:
P 层(presenter)
原先杂糅在 activity/fragment 里面的业务逻辑移到 Presenter中,同时 Presenter 做为 M 和 V 之间交互的桥梁。
由于 Activity 跟 Fragment 生命周期不同,会影响一些弹出框关闭的时机,所以项目中,Activity 跟 Fragment 分别定义了一套基础业务抽象类。
这边以 Activity 基础业务抽象类来演示,所有的 Activity 中用到的 Presenter 都继承 PSCBaseActivityPresenter:
PSCActivityNetTask 主要做网络任务监听并回调到 Presenter 中,还会设置生命周期监听,用于显示加载框。
Presenter 接受到网络回调后,根据接口返回的数据做业务处理,成功或者失败分别通过接口回调到 View 层,刷新界面。
V 层(view)
相信大多数 App 都会有 baseActivity 作为基类,将 Activity 公共部分抽取出来进行封装。
苏宁的基类叫做 SuningActivity/SuningFragment,每个界面都需要把 View 跟 Presenter 绑定/解绑,这些都可以放到基类中。
然后定义 protected abstract TcreatePresenter();将创建 Presenter 步骤交给子类实现。
代码量比较大,这边做了删减,仅保留 MVP 相关的代码,如下:
Activity 实现上面定义的 IView,实现数据的接收,同时会在当前类中创建 Presenter,通过 Presenter 方法调用 Model 中的网络请求。
总结
以上内容就是我们对于 MVP 架构的理解,并在苏宁+项目中实战后分享给大家。
MVC、MVP、MVVM 不管何种模式,都可以实现功能,选择相应模式的时候,要看相对于目前业务来说的,何种模式能够封装变化,让各模块解耦,实现独立变化,减少日后的维护工作和暗藏的风险。
当然我们也不能陷入模式的陷阱,为了使用模式而去套模式。没有好的框架,只有适合的框架,如果大家发现我们当前项目中对于 MVP 的使用不对或者不完善的地方,欢迎提出来,我们一起探讨。
曹银飞,苏宁云商 IT 总部 Android 技术专家,拥有多年 Android 研发和管理经验。曾就职于联创、腾讯等大型互联网公司,现负责苏宁易购 Android 开发部产品研发与技术管理工作,在 Android 项目架构设计,性能优化,团队管理上有多年的实战经验。现致力于打造苏宁智慧零售相关 App,希望将苏宁的零售技术能力发挥到极致。
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】