架构
MVC
在 Android 中,三者的关系如下:
- 模型层(
Model
):主要负责网络请求,数据库处理,I/O
操作,即页面的数据来源。 - 视图层(
View
):对应于xml
布局文件和java代码动态view
部分。 - 控制层(
Controller
):主要负责业务逻辑,在android
中由Activity
承担,同时因为XML
视图功能太弱,所以Activity
既要负责视图的先睡又要加入控制逻辑,承当的功能过多。
由于在 Android
中 xml
布局的功能性太弱,所以 Activity
承担了绝大部分的工作,所以在 Android
中 mvc
更像:
总结:
- 具有一定的分层,
model
解耦,controller
和view
并没有解耦 controller
和view
在Android
中无法做到彻底分离,Controller
变得臃肿不堪- 易于理解、开发速度快、可维护性高
Activity
同时负责View
与Controller
层的工作,违背了单一职责原则Model
层与View
层存在耦合,存在互相依赖,违背了最小知识原则
MVP
View
层:对应于Activity
与XML
,只负责显示UI
,只与Presenter
层交互,与Model
层没有耦合。Presenter
层:主要负责处理业务逻辑,通过接口回调View层。Model
层:主要负责网络请求,数据库处理等操作,这个没有什么变化。
通过引入接口 BaseView
,让相应的视图组件如 Activity
,Fragment
去实现 BaseView
,把业务逻辑放在 presenter
层中,弱化 Model
只有跟 view
相关的操作都由 View
层去完成。
总结:
- 彻底解决了
MVC
中View
和Controller
傻傻分不清楚的问题 - 但是随着业务逻辑的增加,一个页面可能会非常复杂,
UI
的改变是非常多,会有非常多的case
,这样就会造成View
的接口会很庞大 - 更容易单元测试
MVP
架构同样有自己的问题:
Presenter
层通过接口与View
通信,实际上持有了View
的引用。- 但是随着业务逻辑的增加,一个页面可能会非常复杂,这样就会造成
View
的接口会很庞大。
MVVM
MVVM
模式将 Presenter
改名为 ViewModel
,基本上与 MVP
模式完全一致。
唯一的区别是,它采用双向数据绑定(data-binding
):View
的变动,自动反映在 ViewModel
,反之亦然。
MVVM
与MVP
的主要区别在于,不用去主动刷新UI
了,只要Model
数据变了,会自动反映到UI
上。
在 MVP 中 View 和 Presenter 要相互持有,方便调用对方,而在 MVP 中 View 和 ViewModel 通过 Binding进行关联,他们之前的关联处理通过 DataBinding 完成。
总结:
- 很好的解决了 MVC 和 MVP 的问题
- 视图状态较多,ViewModel 的构建和维护的成本都会比较高
- 但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源
升级版MVVM
MVVM
不足:
为保证对外暴露的
LiveData
是不可变的,需要添加不少模板代码并且容易遗忘。为保证数据流的单向流动,
LiveData
向外暴露时需要转化成immutable
,这需要添加不少模板代码,如:1
2
3
4
5
6
7
8
9
10class TestViewModel: ViewModel(){
//为保证对外暴露的LiveData不可变,增加一个状态就要添加两个LiveData变量
private val _pageState: MutableLiveData<PageState> = MutableLiveData()
val pageState: LiveData<PageState> = _pageState
private val _state1: MutableLiveData<String> = MutableLiveData()
val state1: LiveData<String> = _state1
private val _state2: MutableLiveData<String> = MutableLiveData()
val state2: LiveData<String> = _state2
//...
}必须定义一个可变,一个不可变的
View
层与ViewModel
层的交互比较零乱,不成体系。
MVI
MVI
与MVVM
很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图:
其主要分为以下几部分:
Model
:与MVVM
中的Model
不同的是,MVI
的Model
主要是指UI
状态(State
)。例如页面加载状态、控件位置等都是一种UI
状态。View
: 与其他MVX
中的View
一致,可能是一个Activity
或者任意UI
承载单元。MVI
中的View
通过订阅Intent
的变化实现界面刷新。(注意:这里不是Activity
的Intent
)Intent
: 此Intent
不是Activity
的Intent
,用户的任何操作都被包装成Intent
后发送给Model
层进行数据请求。
单向数据流
MVI
强调数据的单向流动,主要分为以下几步:
用户操作以
Intent
的形式通知Model
。Model
基于Intent
更新State
。View
接收到State
变化刷新UI
。
数据永远在一个环形结构中单向流动,不能反向流动:
MVI
使用
总体架构图
我们使用ViewModel
来承载MVI
的Model
层,总体结构也与MVVM
类似,主要区别在于Model
与View
层交互的部分。
Model
层承载UI
状态,并暴露出ViewState
供View
订阅,ViewState
是个data class
,包含所有页面状态。View
层通过Action
更新ViewState
,替代MVVM
通过调用ViewModel
方法交互的方式。
MVI
实例介绍
添加ViewState
与ViewEvent
ViewState
承载页面的所有状态,ViewEvent
则是一次性事件,如Toast
等,如下所示:
1 | data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>) |
我们这里
ViewState
只定义了两个,一个是请求状态,一个是页面数据。ViewEvent
也很简单,一个简单的密封类,显示Toast
与Snackbar
。
ViewState
更新
1 | class MainViewModel : ViewModel() { |
如上所示:
我们只需定义
ViewState
与ViewEvent
两个State
,后续增加状态时在data class
中添加即可,不需要再写模板代码。ViewEvents
是一次性的,通过SingleLiveEvent
实现,当然你也可以用Channel
当来实现。当状态更新时,通过
emit
来更新状态。
View
监听ViewState
1 | private fun initViewModel() { |
如上所示,MVI
使用 ViewState
对 State
集中管理,只需要订阅一个 ViewState
便可获取页面的所有状态,相对 MVVM
减少了不少模板代码。
View
通过Action
更新State
1 | class MainActivity : AppCompatActivity() { |
如上所示,View
通过Action
与ViewModel
交互,通过 Action
通信,有利于 View
与 ViewModel
之间的进一步解耦,同时所有调用以 Action
的形式汇总到一处,也有利于对行为的集中分析和监控。
总结
本文主要介绍了MVC
,MVP
,MVVM
与MVI
架构,目前MVVM
是官方推荐的架构,但仍然有以下几个痛点:
MVVM
与MVP
的主要区别在于双向数据绑定,但由于很多人(比如我)并不喜欢使用DataBindg
,其实并没有使用MVVM
双向绑定的特性,而是单一数据源。当页面复杂时,需要定义很多
State
,并且需要定义可变与不可变两种,状态会以双倍的速度膨胀,模板代码较多且容易遗忘。View
与ViewModel
通过ViewModel
暴露的方法交互,比较凌乱难以维护。
而MVI
可以比较好的解决以上痛点,它主要有以下优势:
强调数据单向流动,很容易对状态变化进行跟踪和回溯。
使用
ViewState
对State
集中管理,只需要订阅一个ViewState
便可获取页面的所有状态,相对MVVM
减少了不少模板代码。ViewModel
通过ViewState
与Action
通信,通过浏览ViewState
和Aciton
定义就可以理清ViewModel
的职责,可以直接拿来作为接口文档使用。
当然MVI
也有一些缺点,比如:
所有的操作最终都会转换成
State
,所以当复杂页面的State
容易膨胀。state
是不变的,因此每当state
需要更新时都要创建新对象替代老对象,这会带来一定内存开销。
软件开发中没有银弹,所有架构都不是完美的,有自己的适用场景,读者可根据自己的需求选择使用。
但通过以上的分析与介绍,我相信使用MVI架构代替没有使用DataBinding
的MVVM
是一个比较好的选择