笔者在公司里研究了一段时间安卓项目,安卓项目现在主流的架构是MVVM(Model-View-ViewModel),在这之前还有MVC(Model-View-Controller)、MVP(Model-View-Presenter),这些架构模式的设计思路,在我看来主要目的其实只有一个——高内聚、低耦合。反正就是让项目代码健壮、高效、易读。把软件模块化,抽象出多个层级,每个层级专门干自己的事。现在网上有很多关于项目架构的讲解文章,但是大多数都讲的很模糊很笼统,每个人讲的意思还可能不一样,笔者在这篇文章中通过自己的理解进行内容的梳理。
MVC与MVP
MVC是较早推出的架构。
| MVC | ||
|---|---|---|
| Model | 数据模型 | 对数据对象的定义,数据库的操作相关 |
| View | 用户视图 | 呈现给用户浏览、交互的页面 |
| Controller | 控制器 | 资源调度者 |
控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。这使得早期的MVC项目中处理逻辑分散,写在View层容易使得View层过于臃肿,于是新的设计理念MVP登场了。
![]()![]()![]()![]()![]()![]()
| MVP | ||
|---|---|---|
| Model | 数据模型 | 对数据对象的定义,数据库操作相关 |
| View | 用户视图 | 呈现给用户浏览、交互的页面 |
| Presenter | P层/中介层/任命层 | 处理View与Model之间的交互,业务处理+操作Model |
从两张表格看上去MVC与MVP仿佛没什么区别,只是由原来的控制层更改为P层。这一改动的目的是:
- View层和Model层的解耦性更强。
- 逻辑集中在P层中,View层只处理视图更新等相关操作,业务逻辑交由P层。
View持有Presenter,Presenter持有View接口。
从架构图上可以看出,早期的MVC中View层可以直接调取Model,比如cshtml代码中
@Model.UserName
@foreach(var item in Model.List)早期的MVC架构View可以根据需要绕过Controller主动调用数据,以达到快速响应,这意味着View层不但持有Controller的引用还可能持有Model的引用。而在MVP中是不允许的,所有View层与Model层的所有交互必须通过P层。
MVP架构中的业务流程是:用户在View层操作输入->P层处理操作逻辑、根据需要调取Model->Model操作数据库->返回结果到P层、P层可拦截器操作(如敏感信息过滤)->P层手动调用View层更新UI显示数据给客户。
View层只负责显示,相当于一张只剩下填充数据填空题卷子。也就是为什么现在流行前后端分离,View层被分出到前端,后端搞微服务极简API,如.NET下的WebAPI,只剩下业务处理和数据库,MVC只剩下MC,View层被彻底解耦。
高内聚、低耦合Level Up!
响应式MVP
初期的项目架构并不是很适合安卓开发,安卓是一个有状态、长生命周期、双向交互、数据会异步改变的系统!
Web系统是通过Http请求后端:用户请求->后端响应 ,一次性渲染、无状态维护,通讯过程中产生的所有临时实例,通讯结束就销毁,不维护通讯的状态,短通讯。MVC最初就是为Web设计的。
在安卓中Activity(View)要实时更新,且要管理各种View(Activity、Fragment、Dialog,可以理解为各种嵌套的大小窗口)的生命周期,Model(数据)可能随时异步变化。在生命周期不同步条件下,当某个View的生命周期结束被销毁,而Model还在跑它的异步请求,拿到数据后想继续更新UI则导致崩溃!安卓生命周期不同步,状态复杂,异步满天飞!MVP架构下,每次更新View都需要P层手动调用View层,且在操作View层之前需要手动判断View层的生命周期,确保安全后再操作。(这些在WPF中也有相似的问题,WPF也是基于生命周期的异步)。
每当数据更新时,都需要手动去更新UI界面,这太麻烦了,如果能让View层订阅数据变量,当数据变量的值更新时自动提醒View层更新就会省事很多,于是响应式MVP-RxJava登场了,RxJava的引入为Android架构带来了响应式编程思想,通过数据流和异步处理简化了组件间通信。
mModel.adLogin(RequestManager.getInstance().handleRequestData(bindAdviceData, Constant.ACTION_BIND_AD_ADVICE))
.as(AutoDispose.<Result<String>>autoDisposable(AndroidLifecycleScopeProvider.from((LifecycleOwner) getView())))
.subscribe(new Consumer<Result<String>>() {
@Override
public void accept(Result<String> stringResult) throws Exception {
screen adviceInfo = mGson.fromJson(stringResult.getoResult(), screen.class);
if (isAttach()) {
getView().dismissLoading();
getView().bindAdvicesSuccess(adviceInfo);
}
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
if (isAttach()) {
getView().showMsg(throwable.getMessage());
getView().dismissLoading();
}
throwable.printStackTrace();
}
});调用model获取数据(这里是API请求的方式),链式调用subscribe订阅数据,更新UI之前手动判断UI的生命周期isAttach(),RxJava结合了生命周期感知组件,AutoDispose对象管理要被更新的UI的生命周期,当生命周期结束时,自动注销订阅。链式调用.map方法执行数据转换,.subscribeOn可指定在哪个线程调用,比如Schedulers.io(),.observeOn可指定在哪个线程观测数据变化,比如AndroidSchedulers.mainThread()。也就是在io线程订阅数据,在主线程观测数据变化,当数据变化时主线程观测到则去更新UI,安卓规定对UI的操作必须在主线程执行(不止安卓,大部分客户端都这么规定,防止内存泄漏,生命周期状态同步错误而崩溃)
MVVM
虽然响应式MVP自动化上了,感觉很爽,但是每次更新UI之前都需要手动判断生命周期,完全靠人肉保证以防止内存泄漏,P层持有V层的引用,始终有内存泄漏的隐患。
注意,安卓只管理V层的生命周期,P层M层等都是人为划分出来的职责层,他们只与V层绑定,他们的生命周期人为手动规划,P层的生命周期大于V层,也就是先把一切东西准备好,再去展示V层给用户看,V层生命周期结束销毁后P层等对象再相继释放内存。
| MVVM | ||
|---|---|---|
| View层 | 用户视图 | 呈现给用户浏览交互的页面 |
| ViewModel层 | 作为View和Model之间的桥梁 | 处理与用户交互相关的逻辑。不直接与View``交互,而是通过数据绑定,生命周期自动感知实现 UI 的自动更新 |
| Model层 | 数据模型 | 对数据对象的定义,数据库操作相关 |
MVVM(Model-View-ViewModel)架构是目前前端最主流的架构,Vue、React、Angular本质都是MVVM。而后端那种不保管状态的系统则不适用。
MVVM的目的是数据驱动架构模式,讲究通过ViewModel层实现View和数据的双向绑定,ViewModel层不再持有View层的引用(更加解耦!),ViewModel层通过LiveData将从Model层取得的数据保存在LiveData变量中,View层去观测ViewModel层中的LiveData变量来响应式更新。ViewModel层只负责暴露出LiveData,任何View都可以去观测,也就是一个ViewModel可以被多个View使用(这是MVVM的优势),View层持有自身绑定的ViewModel层的引用。
View层只负责显示!ViewModel只负责逻辑处理获取数据并暴露出来!Model层只负责操作数据库!更加解耦!各司其职!
LiveData是谷歌为了响应式观测数据变化而封装的工具,他会自动绑定观测者View的生命周期,自动管理防止内存泄漏。
- 自动判断页面是否存活
- 自动在页面销毁时取消订阅
- 自动只在页面可见时更新UI
在业务逻辑允许的情况下,应该尽量让多个View共用一个ViewModel,管理方便。笔者曾让Activity和Fragment拥有各自的ViewModel,这使得ViewModel之间的数据传递变得复杂,徒增异步。
架构因地制宜的优化
项目不是死板的Ctrl+V,MVC、MVP、MVVM在不同项目中根据实际业务情况选择与优化。
比如适合大型CMS、多模块系统的HMC(分层MVC):模块内部自己MVC,模块之间相互调用。
| 分层MVC | |
|---|---|
| Controller层 | 控制器:接受请求(GET/POST),调用Service,返回结果(视图、Json)原则:越轻薄越好 |
| Service层 | 业务逻辑层:校验逻辑、事务控制、多表操作、调用多个Dao原则:系统的大脑 |
| Dao/Repository层 | 仓储层:增删改查、写SQL或ORM、不写任何业务逻辑原则:只和数据库对话 |
| Model/Entity层 | 数据实体模型:只是数据表的映射对象,只有属性,没有逻辑 |
| View层 | 视图,前后端分离可去掉 |
有分层MVC那么也会有分层MVP,思路设计上是一样的。
后端极简API的(比如当下流行的微服务)Rounter-Controller架构(其实是MVC把V分离出去):只有路由+控制器+数据层,轻量架构。
事件驱动架构(Event-Driven):核心是Hook/filter/action,功能之间不直接调用,靠事件触发,优点是极高扩展性,缺点是异步乱,难调试。WordPress网站框架就是这个架构,笔者以前开发过经典游戏Terraria的服务器插件,主要操作也是注册hook去拦截过滤数据,Terraria的架构估计也是事件驱动。
思考
在各行各业的项目中肯定都有相似相通的架构,设计思路上肯定会有通用的可复用的抽象层,如View、Model,比如独立游戏开发中,游戏客户端是前端,也有类似的MVVM架构,服务端是后端,在这里应该只有不追求高实时性的游戏才会采用RESTFulAPI,像王者荣耀那种高实时同步的游戏据笔者了解这方面游戏公司的岗位招人要求是熟悉ECS架构。
这里趴着一只猫