Android开发经验谈Android开发Android技术知识

Android 开源社区“Model-View-Intent”架

2022-05-16  本文已影响0人  程序老秃子

前言

Android 开发的架构模式最流行的莫过于 Jetpack 架构组件提供的强大易用的 MVVM 实现;去年公司要重构一块老旧的重要业务,原先的 Java + 无架构实现被我们全面切换到 Kotlin + Coroutines + Jetpack AAC。总体效果令我们颇为满意,也没有发现什么明显的缺陷与短板

Jetpack AAC 虽然很赞,但它不能用于 KMM,于是我们在开源社区找到了一个“替代品”——MVIKotlin

MVIKotlin 是一款实现 MVI 模式的框架,它不仅能用于 KMM,还能用于 JavaScript、JVM、LinusX64、MacX64 等多个 Kotlin Target

MVI架构模式

MVI表示的是Model-View-Intent.这个模式最近才被引入Android;受到Cycle.js框架的思路影响,是基于单向圆柱流的原理进行工作的

● Model:

不像其它架构模式的Model,在MVI架构中Model表示UI的状态。举个例子,UI可能会存在不同的状态,比如数据加载Data Loading,加载完成Loaded,用户的动作造成数据的改变,错误,用户当前屏幕位置状态等等,每一个状态都被存储到Model对象中

● View:

在MVI中View作为接口,可以在Activity或者Fragment中实现。这个接口的意思就是需要有一个容器来接收不同状态并进行展示。它们使用可观察的Intent(这里的Intent并不是传统的Android Intent,这里应该取Intent的本意,意图)来回应用户的动作

● Intent:

这个并不是之前所提到的Android Intent.用户的动作的结果作为输入值传递给Intent。回过来,我们可以说,我们将会发送Models作为输入给Intent,然后通过View加载这些Model的状态

何为MVI?

MVI即Model-View-Intent,它受Cycle.js前端框架的启发,提倡一种单向数据流的设计思想,非常适合数据驱动型的UI展示项目

Model: 与其他MVVM中的Model不同的是,MVI的Model主要指UI状态(State);当前界面展示的内容无非就是UI状态的一个快照:例如数据加载过程、控件位置等都是一种UI状态

View: 与其他MVX中的View一致,可能是一个Activity、Fragment或者任意UI承载单元;MVI中的View通过订阅Intent的变化实现界面刷新(不是Activity的Intent、后面介绍)

Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model进行数据请求

单向数据流

用户操作以Intent的形式通知Model => Model基于Intent更新State => View接收到State变化刷新UI

数据永远在一个环形结构中单向流动,不能反向流动

这种单向数据流结构的MVI有什么优缺点呢?

优点

● UI的所有变化来自State,所以只需聚焦State,架构更简单、易于调试

● 数据单向流动,很容易对状态变化进行跟踪和回溯

● state实例都是不可变的,确保线程安全

● UI只是反应State的变化,没有额外逻辑,可以被轻松替换或复用

缺点

● 所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀

● state是不变的,每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销

● 有些事件类的UI变化不适合用state描述,例如弹出一个toast或者snackbar talk is cheap, show me the code

单向数据流的不变性

● 咱们的View层应该有一个相似view.render(...)的方法

● 咱们的Model是不可变的,所以不可直接修改Model

● View的渲染有且只有一个来源:即业务逻辑

点击事件 下沉 到业务逻辑层。业务逻辑知道当前的Model(例如,持有一个私有的成员Model,它表明着当前的状态), 这以后根据旧的Model,建立一个新的带有增量/减量值的Model

单向数据流,业务逻辑做为单一源用于建立不可变的Model实例,但对于一个计数器来说未免有点小题大作,不是吗?诚然,是的,计数器只是一个简单的应用程序;

大多数应用程序都是以简单的应用程序开始,但复杂性增加很快——从个人角度来看,单向数据流和不可变模型是必要的,这会使简单的应用程序,在复杂性递增的同时,依然保持着简单(对开发者而言)

可调试和可重现的状态

单向数据流保证了咱们的应用程序易于调试。下次咱们从Crashlytics得到崩溃报告时,咱们能够轻松地重现并修复此崩溃,由于全部必需的信息都已附加到崩溃报告中了

什么叫作必需的信息?

那就是当前的Model和用户用户在崩溃发生时想要执行的操做(好比,点击减量按钮)。这就是咱们重现此次崩溃所需的所有信息,这些信息很是容易收集并附加在崩溃报告中

若是没有单项数据流(好比,对EventBus的滥用,或者将CounterModels的私有域暴露出来),或者没有不变性(这会致使咱们不知道谁实际更改了Model),那么bug的复现就没那么容易了

为什么是MVI?

由于没有明确的状态管理,随着应用程序的增长或添加功能或事先没有计划的功能,视图渲染和业务逻辑可能会变得有点混乱,老实说,这种情况经常发生,很少从项目规格一开始就清楚和全面定义所有功能,应用程序代码库的可扩展性越强,接受新想法和更新就越灵活

状态危机导致业务逻辑和 UI 渲染纠缠不清,这是怎么发生的?

● Presenter/ViewModel有自己的状态

● 业务逻辑产生自己的状态

● 尝试同步上述两种状态

● 如果对Presenter/ViewModel的输入没有明确的管理->处理输出的纠结结果->混乱的业务逻辑和视图渲染->代码闻起来像碎片/活动变成了一个黑洞类(通过吸引更多责任)或有不同的类(当您想更新,但由于许多不同的原因不得不以多种不同的方式更改类时),这是由于关注的分离不良

调整MVI架构模式有什么好处?

状态管理利用不变性来拥有单一的真实来源

单向数据流

● 可复制状态->简单代码重用

● 更好地分离关注->可维护性

MVI 模式的改动

MVI 模式的改动在于将 View 和 ViewModel 之间的多数据流改为基于 ViewState 的单数据流MVI 将代码分为以下四个部分:

View: Activity 和 Layout XML 文件,与 MVVM 中 View 的概念相同

Intent: 定义数据操作,是将数据传到 Model 的唯一来源,相比 MVVM 是新的概念

ViewModel: 存储视图状态,负责处理表现逻辑,并将 ViewState 设置给可观察数据容器

ViewState: 一个数据类,包含页面状态和对应的数据

在实现细节上,View 和 ViewModel 之间的多个交互(多 LiveData 数据流)变成了单数据流。无论 View 有多少个视图状态,只需要订阅一个 ViewState 便可以获取所有状态,再根据 ViewState 去响应。当然,实践中应该根据状态之间的关联程度来决定数据流的个数,不应该为了使用 MVI 模式而强行将多个无关的状态压缩在同一个数据流中。

唯一可信源: 数据只有一个来源(ViewModel),与 MVVM 的思想相同

单数据流: View 和 ViewModel 之间只有一个数据流,只有一个地方可以修改数据,确保数据是安全稳定的。并且 View 只需要订阅一个 ViewState 就可以获取所有状态和数据,相比 MVVM 是新的特性

响应式: ViewState 包含页面当前的状态和数据,View 通过订阅 ViewState 就可以完成页面刷新,相比于 MVVM 是新的特性

但 MVI 本身也存在一些缺点:

State 膨胀: 所有视图变化都转换为 ViewState,还需要管理不同状态下对应的数据。实践中应该根据状态之间的关联程度来决定使用单流还是多流

内存开销: ViewState 是不可变类,状态变更时需要创建新的对象,存在一定内存开销

局部刷新: View 根据 ViewState 响应,不易实现局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 来刷新来减少不必要的刷新

不过,MVI 并不是一个全新的设计模式,其背后设计理念与 Redux 模式如出一辙;在 Redux 里完全可以找到与 MVI 相同的各个要素,而且明显 Redux 的命名方式更加清晰无歧义

这个架构的核心思想

● 我们在MVVM架构中包括一个实际的不可变的Model层,我们的视图依赖于这个Model的状态变化。这样一来,ViewModel就必须修改和公开这个单一的Model

● 为了避免冗余和简化这种架构在多个地方的使用,我创建了两个抽象类,一个用于我们的视图(为Activity、Fragment、自定义视图分开),一个用于ViewModel

AacMviViewMode;一个通用的基类来创建ViewModel;它需要三个类STATE、EFFECT和EVENT

如你所见,我们有viewState。STATE和viewEffect。EFFECT和两个私有的LiveData容器viewStates。MutableLiveData 和viewEffect: SingleLiveEvent ,它们通过公共函数viewStates()和viewEffects()被暴露出来;请注意,我们正在扩展AndroidViewModel,因为它将允许我们在需要时使用应用程序上下文(仅);此外,我们正在记录每个viewEvent,我们将处理这些事件

结语

MVI模式为了解决什么问题?结论:为了在MVC架构模式的思想上实现响应式编程范式;MVC主要的目的是将View和Model隔离;MVI在隔离View和Model的基础上,实现了响应式编程(也就是reactive编程)

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

PS:有问题欢迎指正,可以在评论区留下你的建议和感受; 欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

上一篇下一篇

猜你喜欢

热点阅读