Android开发拾遗:MVVM与MVI
创建于:发布于:文集:Android开发拾遗 如果想要写一个可运行的应用,将所有的代码都放在同一个文件里并不会影响其编译运行,但在实践中,当一个软件的功能越来越复杂,代码量不断增多,为了项目的可维护性,一般需要遵循一些模式将代码拆分开。像微软的ASP.NET这类Web应用框架,就为开发者预设了一套模板,使用被称为「MVC」的架构模式,要求用户将代码分散到不同的目录,各自继承一些特定的类,致力于将UI表现层与业务逻辑解耦。
架构模式(architectural pattern)是软件架构中在给定环境下,針對常遇到的问题的、通用且可重用的解决方案。
近期在浏览Android开发相关内容时常看到「MVI」架构模式,但与ASP.NET不同,Android并没有一个很强的约束要求开发者必须以某种模式写代码,网上找到的一些对于MVI的介绍也没有把它讲得很清楚。所以这次就结合实践中的一些问题,讲讲我对MVI的理解。
为了说明MVI,需要回顾一下过去的Android开发范式。在Android使用XML
构建视图的时代,受推崇的架构模式是MVVM,这种模式在很多流行的GUI框架——如Vue.js——中被广泛使用。
MVVM是model-view-viewmodel的缩写,ViewModel层作为View层和Model层之间的桥梁,避免视图和模型之间的直接交互。
以一个购物应用为例,ViewModel层可以继承官方库中的ViewModel类:
在XML布局中绑定ViewModel的数据:
ViewModel与XML View之间建立了数据的双向绑定,ViewModel中的LiveData变更会直接体现在UI上,不用手写数据变更监听的代码,实际应用中,ViewModel层可能会从Model层获取商品数据,不同商品有不同价格,ViewModel处理价格的计算逻辑,View只负责最终结果的呈现。
这样就实现了关注点分离,利于代码的维护,例如可以在不改变ViewModel的情况下使用Android新的UI库——Jetpack Compose,只需要在Compose中使用:
就可以了。
那这么做有没有什么缺陷呢?MVI和这种模式的区别在哪?
虽然一般来说在MVVM中,数据绑定是双向的,但为了数据安全,通常只向View暴露一个只读的LiveData,避免意外的数据修改:
这些state可能会散落在UI的各处,给每个state都重复一遍私有和公开的声明也让代码有点繁瑣。如果要做单元测试,测试代码也会不简洁。MVI模式的核心就在于:单向数据流、单一不可变的状态对象及事件驱动的状态管理。
MVI是model-view-intent的编写:
在实际的代码中,通常会封装一个State数据类:
仍然可以使用ViewModel类,将业务逻辑放在ViewModel中,但这次只有一个state对象:
但这次ViewModel不直接暴露可以更新state的方法,而是使用一个自定义的Intent:
将整个视图和ViewModel分开来:
在实际应用中,Intent不一定必须由ViewModel处理,例如我用的某个第三方SDK要求在Activity上调用某个方法:
所有的状态数据都集中在了一处,数据只能单向流入UI层,用户交互产生Intent,对Intent的处理决定是否需要更新状态。整个UI可以脱离ViewModel的业务逻辑代码,单独做测试、预览。
相比之下,MVI不像MVC或MVVM那样定义清晰,Android官方也没有强制开发者必须遵守这个模式。可以将其看作在MVVM架构上的助力View和ViewModel解耦的范式,解耦合通常是好的,但要注意没有银弹,单一状态对象也可能带来一些不简洁的代码,如每次状态变更都需要copy:
还有由于Kotlin不是像Haskell一样天生不可变的纯函数式语言,頻繁的copy也可能会影响性能(得看JIT的优化)?