MVVM/MVI in Android
还记得刚开始接触Android开发工作,哪有什么架构模式选择,几乎所有逻辑都放到Activity中。随着时间推移,后来听过MVC,用过MVP,MVVM和对MVI模糊的理解(为何是模糊的理解,因为我在使用的时候,并没有觉得和MVVM有差异,也不知道是否正确地使用>_<)
而这两年通过在项目实践过程中的经验积累,多少对近年比较流行的MVVM/MVI有了一定的理解。想尝试写篇博文,如有错误请指出。
MVVM
MVVM是Model-View-ViewModel的缩写,是前些年在Android中比较火的一个词。是Android开发中备受欢迎的架构模式,其架构图如下:
图片来自MVVM (Model View ViewModel) Architecture Pattern in Android
Model: 模型,业务领域模型
View: 用户界面,展示给用户能看到的。可以是Activit/Fragment(xml)或者Compose中的可组合项。一般通过观察的方式绑定业务模型数据实现界面同步更新
ViewModel: 前两者数据连接的桥梁。其职责有
- 根据View操作事件,实现业务逻辑处理
- 实现View和Model数据绑定,同步更新
基本工作流程:
- 界面在初始化完成后,ViewModel会将View和Model进行双向绑定(如:xxxBinding.setViewModel(**))
- 用户操作界面,触发View操作事件
- ViewModel收到View的操作事件,实现业务逻辑处理
- 业务逻辑处理结果导致Model内容改变,用户界面观察到内容改变从而更新界面
优点:
- 解偶度高。View和Model之间完全解偶
- 提高开发效率。基于双向绑定,在开发过程中无需手动更新UI,关注点侧重放在业务逻辑和Model内容关系上,降低开发复杂性和工作量
缺点:
- 双向绑定导致Model变化难以跟踪,增加错误调试难度
MVI
MVI,Model-View-Intent的缩写,网上说法各种各样,唯独对于Intent层基本都是理解为用户意图。来自官方的架构图:
MVI有两大特点:
- 状态统一管理
MVI中还有一个概念,UiState。它一般是不可变的(data class),每一个界面对应一个UiState - 数据单向流动
根据上图,了解UDF状态是自上而下,而事件则自下而上形成一个环
以下则是笔者参考并结合自己开发过程的理解来说明的
Model: 如MVVM一样,领域模型。UiState根据Model变化而改变
View: 如MVVM一样,展示给用户的界面
Intent: 用户意图,用户对界面功能的操作意图。比如想要返回而点击按钮
基本工作流程: (参考自官方文档)
- ViewModel持有并展露UiState,View会根据UiState渲染界面
- 用户通过意图操作通知ViewModel
- ViewModel根据用户意图处理逻辑并更新状态(UiState)
- 对于任何状态都是重复以上步骤
优点:
- 数据单向流动保证了数据的一致性,提高了可测试性和调试便利
- 统一管理状态,一个界面对应一个状态,加强了代码可读性
缺点:
- 由于Intent的存在,需要定义Event并通过UI传递给ViewModel,会导致ViewModel逻辑代码增加。
说一说
对于MVVM的实现,相信大多开发者都跟笔者差不多,使用DataBinding/LiveData吧,不过后来Kotlin出现了StateFlow/SharedFlow,LiveData就也被替换了。
MVVM with DataBinding
对于DataBinding,对于目前声明式UI的实现无需考虑。在命令式UI时候,通过外层加<layout></layout>
使得代码生成Binding类,然后通过set方法持有Model对象实现View <-> Model之间的绑定。
不过随着界面的增加,编译速度会越来越慢,而且出问题的时候相对难定位。在Model中,更是几乎每个属性都需要用ObservableField,如遇到不支持的Binding还需要手动添加BindingAdapter支持等。MVVM with LiveData/StateFlow
在这套MVVM实现方式中,你需要更多了解LiveData和StateFlow,因为出问题的时候很多是因为对LiveData/StateFlow原理实现不理解,如为什么突然界面就刷新了?为什么数据被覆盖而显示错误的界面等。MVI
在学习Jetpack Compose过程中,笔者使用最多的就是MVI架构模式了。因为MVI中设计的UiState和Jetpack Compose重组刷新界面正好可以一一对应。在开发过程中会根据需求和界面定义好各种UiState,然后只需要考虑好这些UiState在ViewModel经过逻辑之后的输出正确即可(对于单元测试挺友好的)。
在使用MVI开发过程中,也曾有些时候矛盾过,创建这么多类真的有必要吗?但是好像为了按照架构来实现又不能删掉。
结语
其实在实际开发过程中,大多时候MVVM也渐渐从双向绑定走向了数据单向流实现。从Google官方对架构的介绍中可以看出,类似的MVI很大程度上和MVVM相同的。
在Google介绍的MVI中,每个UiState对应一个UI界面,这对于声明式UI特别友好。比如Jetpack Compose中,收到UiState触发重组即可。但对于传统的xml开发界面,你会发现判断条件并没有少多少。
由于这种架构模式的设计,为了解耦,层级分明导致需要更多类来实现,对于一些简单的app或者feature来说,笔者有时候看着空荡荡的类会觉得其实没必要。所以在接受范围内做一些妥协并没有什么。
其实不管哪种实现,在开发过程中,都会因为需求不同而遇到各种各样问题的,没有任何一种解决方案是完美的。 开发的过程要有自己的思想和理解,才能更有效提升。