MVVM 进阶版:MVI 架构了解一下~
前言
Android
开发发展到今天已经相当成熟了,各种架构大家也都耳熟能详,如MVC
,MVP
,MVVM
等,其中MVVM
更是被官方推荐,成为Android
开发中的显学。
不过软件开发中没有银弹,MVVM
架构也不是尽善尽美的,在使用过程中也会有一些不太方便之处,而MVI
可以很好的解决一部分MVVM
的痛点。
本文主要包括以下内容
MVC
,MVP
,MVVM
等经典架构介绍MVI
架构到底是什么?MVI
架构实战
需要重点指出的是,标题中说
MVI
架构是MVVM
的进阶版是指MVI
在MVVM
非常相似,并在其基础上做了一定的改良,并不是说MVI
架构一定比MVVM
适合你的项目
各位同学可以在分析比较各个架构后,选择合适项目场景的架构
经典架构介绍
MVC
架构介绍
MVC
是个古老的Android
开发架构,随着MVP
与MVVM
的流行已经逐渐退出历史舞台,我们在这里做一个简单的介绍,其架构图如下所示:MVC
架构主要分为以下几部分
- 视图层(
View
):对应于xml
布局文件和java
代码动态view
部分 - 控制层(
Controller
):主要负责业务逻辑,在android
中由Activity
承担,同时因为XML
视图功能太弱,所以Activity
既要负责视图的显示又要加入控制逻辑,承担的功能过多。 - 模型层(
Model
):主要负责网络请求,数据库处理,I/O
的操作,即页面的数据来源
由于android
中xml
布局的功能性太弱,Activity
实际上负责了View
层与Controller
层两者的工作,所以在android
中mvc
更像是这种形式:
因此MVC
架构在android
平台上的主要存在以下问题:
Activity
同时负责View
与Controller
层的工作,违背了单一职责原则Model
层与View
层存在耦合,存在互相依赖,违背了最小知识原则
MVP
架构介绍
由于MVC
架构在Android
平台上的一些缺陷,MVP
也就应运而生了,其架构图如下所示MVP
架构主要分为以下几个部分
View
层:对应于Activity
与XML
,只负责显示UI
,只与Presenter
层交互,与Model
层没有耦合Presenter
层: 主要负责处理业务逻辑,通过接口回调View
层Model
层:主要负责网络请求,数据库处理等操作,这个没有什么变化
我们可以看到,MVP
解决了MVC
的两个问题,即Activity
承担了两层职责与View
层与Model
层耦合的问题
但MVP
架构同样有自己的问题
Presenter
层通过接口与View
通信,实际上持有了View
的引用- 但是随着业务逻辑的增加,一个页面可能会非常复杂,这样就会造成
View
的接口会很庞大。
MVVM
架构介绍
MVVM
模式将 Presenter
改名为 ViewModel
,基本上与 MVP
模式完全一致。
唯一的区别是,它采用双向数据绑定(data-binding
):View
的变动,自动反映在 ViewModel
,反之亦然MVVM
架构图如下所示:
可以看出MVVM
与MVP
的主要区别在于,你不用去主动去刷新UI
了,只要Model
数据变了,会自动反映到UI
上。换句话说,MVVM
更像是自动化的MVP
。
MVVM
的双向数据绑定主要通过DataBinding
实现,不过相信有很多人跟我一样,是不喜欢用DataBinding
的,这样架构就变成了下面这样
View
观察ViewModle
的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定,所以其实MVVM
的这一大特性我其实并没有用到View
通过调用ViewModel
提供的方法来与ViewMdoel
交互
小结
MVC
架构的主要问题在于Activity
承担了View
与Controller
两层的职责,同时View
层与Model
层存在耦合MVP
引入Presenter
层解决了MVC
架构的两个问题,View
只能与Presenter
层交互,业务逻辑放在Presenter
层MVP
的问题在于随着业务逻辑的增加,View
的接口会很庞大,MVVM
架构通过双向数据绑定可以解决这个问题MVVM
与MVP
的主要区别在于,你不用去主动去刷新UI
了,只要Model
数据变了,会自动反映到UI
上。换句话说,MVVM
更像是自动化的MVP
。MVVM
的双向数据绑定主要通过DataBinding
实现,但有很多人(比如我)不喜欢用DataBinding
,而是View
通过LiveData
等观察ViewModle
的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定
MVI
架构到底是什么?
MVVM
架构有什么不足?
要了解MVI
架构,我们首先来了解下MVVM
架构有什么不足
相信使用MVVM
架构的同学都有如下经验,为了保证数据流的单向流动,LiveData
向外暴露时需要转化成immutable
的,这需要添加不少模板代码并且容易遗忘,如下所示
class 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
//...
}
如上所示,如果页面逻辑比较复杂,ViewModel
中将会有许多全局变量的LiveData
,并且每个LiveData
都必须定义两遍,一个可变的,一个不可变的。这其实就是我通过MVVM
架构写比较复杂页面时最难受的点。
其次就是View
层通过调用ViewModel
层的方法来交互的,View
层与ViewModel
的交互比较分散,不成体系
小结一下,在我的使用中,MVVM
架构主要有以下不足
- 为保证对外暴露的
LiveData
是不可变的,需要添加不少模板代码并且容易遗忘 View
层与ViewModel
层的交互比较分散零乱,不成体系
MVI
架构是什么?
MVI
与 MVVM
很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图如下所示
其主要分为以下几部分
Model
: 与MVVM
中的Model
不同的是,MVI
的Model
主要指UI
状态(State
)。例如页面加载状态、控件位置等都是一种UI
状态View
: 与其他MVX
中的View
一致,可能是一个Activity
或者任意UI
承载单元。MVI
中的View
通过订阅Model
的变化实现界面刷新Intent
: 此Intent
不是Activity
的Intent
,用户的任何操作都被包装成Intent
后发送给Model
层进行数据请求
单向数据流
MVI
强调数据的单向流动,主要分为以下几步:
- 用户操作以
Intent
的形式通知Model
Model
基于Intent
更新State
View
接收到State
变化刷新UI。
数据永远在一个环形结构中单向流动,不能反向流动:
上面简单的介绍了下MVI
架构,下面我们一起来看下具体是怎么使用MVI
架构的
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
等,如下所示
data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)
sealed class MainViewEvent {
data class ShowSnackbar(val message: String) : MainViewEvent()
data class ShowToast(val message: String) : MainViewEvent()
}
- 我们这里
ViewState
只定义了两个,一个是请求状态,一个是页面数据 ViewEvent
也很简单,一个简单的密封类,显示Toast
与Snackbar
ViewState
更新
class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData<MainViewState> = MutableLiveData()
val viewStates = _viewStates.asLiveData()
private val _viewEvents: SingleLiveEvent<MainViewEvent> = SingleLiveEvent()
val viewEvents = _viewEvents.asLiveData()
init {
emit(MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList()))
}
private fun fabClicked() {
count++
emit(MainViewEvent.ShowToast(message = "Fab clicked count $count"))
}
private fun emit(state: MainViewState?) {
_viewStates.value = state
}
private fun emit(event: MainViewEvent?) {
_viewEvents.value = event
}
}
如上所示
- 我们只需定义
ViewState
与ViewEvent
两个State
,后续增加状态时在data class
中添加即可,不需要再写模板代码 ViewEvents
是一次性的,通过SingleLiveEvent
实现,当然你也可以用Channel
当来实现- 当状态更新时,通过
emit
来更新状态
View
监听ViewState
private fun initViewModel() {
viewModel.viewStates.observe(this) {
renderViewState(it)
}
viewModel.viewEvents.observe(this) {
renderViewEvent(it)
}
}
如上所示,MVI
使用 ViewState
对 State
集中管理,只需要订阅一个 ViewState
便可获取页面的所有状态,相对 MVVM
减少了不少模板代码。
View
通过Action
更新State
class MainActivity : AppCompatActivity() {
private fun initView() {
fabStar.setOnClickListener {
viewModel.dispatch(MainViewAction.FabClicked)
}
}
}
class MainViewModel : ViewModel() {
fun dispatch(action: MainViewAction) =
reduce(viewStates.value, action)
private fun reduce(state: MainViewState?, viewAction: MainViewAction) {
when (viewAction) {
is MainViewAction.NewsItemClicked -> newsItemClicked(viewAction.newsItem)
MainViewAction.FabClicked -> fabClicked()
MainViewAction.OnSwipeRefresh -> fetchNews(state)
MainViewAction.FetchNews -> fetchNews(state)
}
}
}
如上所示,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
是一个比较好的选择~
更多
关于MVI
架构更佳实践,支持局部刷新,可参见: MVI 架构更佳实践:支持 LiveData 属性监听
关于MVI
架构封装,优雅实现网络请求,可参见: MVI 架构封装:快速优雅地实现网络请求
项目地址
本文所有代码可见:github.com/shenzhen201…
作者:程序员江同学
链接:https://juejin.cn/post/7022624191723601928
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。