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数据绑定,同步更新

基本工作流程:

  1. 界面在初始化完成后,ViewModel会将View和Model进行双向绑定(如:xxxBinding.setViewModel(**))
  2. 用户操作界面,触发View操作事件
  3. ViewModel收到View的操作事件,实现业务逻辑处理
  4. 业务逻辑处理结果导致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: 用户意图,用户对界面功能的操作意图。比如想要返回而点击按钮

基本工作流程: (参考自官方文档

  1. ViewModel持有并展露UiState,View会根据UiState渲染界面
  2. 用户通过意图操作通知ViewModel
  3. ViewModel根据用户意图处理逻辑并更新状态(UiState)
  4. 对于任何状态都是重复以上步骤

优点:

  • 数据单向流动保证了数据的一致性,提高了可测试性和调试便利
  • 统一管理状态,一个界面对应一个状态,加强了代码可读性

缺点:

  • 由于Intent的存在,需要定义Event并通过UI传递给ViewModel,会导致ViewModel逻辑代码增加。

说一说

对于MVVM的实现,相信大多开发者都跟笔者差不多,使用DataBinding/LiveData吧,不过后来Kotlin出现了StateFlow/SharedFlow,LiveData就也被替换了。

  1. MVVM with DataBinding
    对于DataBinding,对于目前声明式UI的实现无需考虑。在命令式UI时候,通过外层加<layout></layout>使得代码生成Binding类,然后通过set方法持有Model对象实现View <-> Model之间的绑定。
    不过随着界面的增加,编译速度会越来越慢,而且出问题的时候相对难定位。在Model中,更是几乎每个属性都需要用ObservableField,如遇到不支持的Binding还需要手动添加BindingAdapter支持等。

  2. MVVM with LiveData/StateFlow
    在这套MVVM实现方式中,你需要更多了解LiveData和StateFlow,因为出问题的时候很多是因为对LiveData/StateFlow原理实现不理解,如为什么突然界面就刷新了?为什么数据被覆盖而显示错误的界面等。

  3. 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来说,笔者有时候看着空荡荡的类会觉得其实没必要。所以在接受范围内做一些妥协并没有什么。

其实不管哪种实现,在开发过程中,都会因为需求不同而遇到各种各样问题的,没有任何一种解决方案是完美的。 开发的过程要有自己的思想和理解,才能更有效提升。