全称
MVC:Model-View-Controller
MVP:Model-View-Presenter
MVVM:Model-View-ViewModel
MVI:Model-View-Intent
● 与MVC,MVP或MVVM一样,MVI是一种体系结构设计模式,可帮助我们更好地组织代码以创建健壮且可维护的应用程序
● 它与Flux或Redux属于同一家族,由AndréMedeiros首次引入;该首字母缩略词是由“模型”,“视图”和“意图”这两个词的紧缩形成的
为什么推荐使用MVI,MVI是基于什么提出的
答:主要为了ViewModel层和View层的交互由双向转化为单向,并且规范交互数据传输
● android端由mvc到mvp再到mvvm最后到mvi,每一次的变化都让代码分层更加清晰,目前MVVM的缺点是ViewModel和view的交互还是属于双向交互,viewModel和Model的处理界限也比较模糊,所以提出MVI,MVI其实是基于MVVM, 在View和ViewModel中增加了Intent来作为中间传输,通过响应编程更新UI实现的
● 这样不仅规范View与ViewModel交互,且将交互顺序由View—>ViewModel->View 的双向交互变为View->Intent->ViewModel->State->View的环形交互,通过Intent和State来解决ViewModel与Model的界限模糊问题
● 也就是说ViewModel现在可以不关心如何被view触发,如何刷新UI,也不关心当前有多少
数据模型,只用来维护Intent和state管理(再直白些就是intent就是view调用viewModel的中间层,state就是viewModel回调view的中间层,model通过intent和state去管理,看起来会更加简洁)
为何使用MVI模式
● 我曾经有一个瞬间觉的个人Model定义全都是错的;通过在各类安卓开发论坛也好主题也罢的讨论和头疼的研究
● 不管如何,最终我选择使用rxjava和Model-View-Intent(MVI)的方式构建响应式的安卓应用程序,就像这种组合我之前是没有尝试过同样,我建立是十分被动的
● 固然,你也会,可是,你会比我好不少
Model 的定义
定义的诚然,有不少模式将"View"和"Model"分离
● 定义的在安卓开发领域最出名的当属Model-View-Controller(MVC),Model-View_Presenter(MVP)和Model-View-ViewModel(MVVM)。你能够从名字看出什么东西么?他们都有Model;可是,我发现大多数时间,我根本没有用Model
例子:仅仅是在后台加载一个persons的列表,一个传统的MVP模式的代码是这样的:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n379" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class PersonsPresenter extends Presenter<PersonsView> {
public void load(){
getView().showLoading(true); // 显示一个加载进度条
backend.loadPersons(new Callback(){
public void onSuccess(List<Person> persons){
getView().showPersons(persons); // 显示人列表
}
public void onError(Throwable error){
getView().showError(error); // 显示错误信息
}
});
}
}</pre>
可是到底什么是"Model"?后台请求是Model?不是,Model应当是业务逻辑;它是做为结果的列表?不是,它仅仅只作一件事情,就是咱们View显示所须要的东西,像加载指示器或错误信息;所以,真正的Model“长”什么样的?
若是按照我对View的理解,那么,Model类应当是这样的:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n382" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class PersonsModel {
// 在真实的项目中,须要定义为私有的
// 而且咱们须要经过getter和setter来访问它们
final boolean loading;
final List<Person> persons;
final Throwable error;
public(boolean loading, List<Person> persons, Throwable error){
this.loading = loading;
this.persons = persons;
this.error = error;
}
}</pre>
那么Presenter应该“长”这样的:多线程
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n384" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class PersonsPresenter extends Presenter<PersonsView> {
public void load(){
getView().render( new PersonsModel(true, null, null) ); // 显示一个加载进度条
backend.loadPersons(new Callback(){
public void onSuccess(List<Person> persons){
getView().render( new PersonsModel(false, persons, null) ); // 显示人列表
}
public void onError(Throwable error){
getView().render( new PersonsModel(false, null, error) ); // 显示错误信息
}
});
}
}</pre>
● 如今在屏幕上的View有了一个将被渲染上去的Model;这个概念其实不是什么新概念;最开始的被Trygve Reenskaug在1979年定义的MVC模式的Model的定义几乎一致:View观察Model的变化
● 不幸的是,MVC这个术语被滥用来描述太多不一样的模式,它们与最原始的MVC定义有了出入。例如,后端工程师使用MVC框架,iOS工程师有ViewController,在安卓开发中MVC的真正含义是什么?Activities是Controller?那么ClickListener意味着什么?如今MVC与最初被Reenskaug定义的MVC来说,这个术语被误解,滥用和错误使用
Model须要解决咱们在安卓开发中常常遇到的问题:框架
● 状态问题
● 屏幕方向问题
● 在页面堆栈中导航
● 进程死亡
● 单向数据流的不变性
● 可调试和可重现的状态
● 测试
MVI详解
1、Intent
是为了让 View
和 ViewModel
更加解耦。这一点连自圆其说都做不到。View 依然持有 ViewModel
,解耦从何谈起?反倒是现在不仅持有了 ViewModel
,还会和一群 Intent
耦合,这明显是增加耦合。
2、Intent
使得View
和 ViewModel
的契约更加清晰。说的没错,View
向 ViewModel
发送命令的全集能通过 Intent
一览无余,但浏览ViewModel
的公共方法不是有同样的效果吗?
3、MVI 强调对UI State
的集中管理,只需要订阅一个 ViewState
便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。
对于复杂界面只订阅一个 State 的话会痛苦不堪的(详见“真唯一数据源”小节)。MVI 整出个“唯一数据源”原来是为了减少模板代码?完美避开了重点~
4、对于 State 来说添加状态只需要添加一个属性,降低了ViewModel与View层的通信成本,将业务逻辑集中在ViewModel中,View层只需要订阅状态然后刷新即可
难道 MVVM 中增加状态不是添加一个属性?难道 MVVM 中 View 层不是订阅状态即可?难道 MVVM 中业务逻辑不是集中在 ViewModel 中?
5、MVVM 的痛点之一:当页面复杂时,需要定义很多 State,并且需要定义可变与不可变两种,状态会以双倍的速度膨胀,模板代码较多且容易遗忘 这不是 MVVM 的痛点,而是使用不当造成的副作用。MVVM 中的 M 被错误的理解并使用,如果它能做到唯一可信数据源,就不存在该痛点了。另外 MVI 中数据持有者也有可变和不可变两个版本,这样做是为了确保唯一可信数据源
MVI
架构次要有以下长处
1、强调数据单向流动,很容易对状态变动进行跟踪和回溯,在数据一致性,可测试性,可维护性上都有肯定劣势
2、强调对UI State
的集中管理,只须要订阅一个ViewState
便可获取页面的所有状态,绝对 MVVM
缩小了不少模板代码
3、增加状态只须要增加一个属性,升高了ViewModel
与View
层的通信老本,将业务逻辑集中在ViewModel
中,View
层只须要订阅状态而后刷新即可
● 当然在软件开发中没有最好的架构,只有最合适的架构,各位可依据状况选用适宜我的项目的架构,实际上在我看来Google
在指南中举荐应用MVI
而不再是MVVM
,很可能是为了对立Android
与Compose
的架构
● 因为在Compose
中并没有双向数据绑定,只有单向数据流动,因而MVI
是最适宜Compose
的架构。
● 当然如果你的我的项目中没有应用DataBinding
,或者也能够开始尝试一下应用MVI
,不应用DataBinding
的MVVM
架构切换为MVI
老本不高,切换起来也比较简单,在易用性,数据一致性,可测试性,可维护性等方面都有肯定劣势,后续也能够无缝切换到Compose
小结
● MVI 用数据流来理解界面刷新:界面是数据流的起点(生产者)也是终点(消费者),界面发出的数据叫事件,事件会用响应式编程的方式被变换为状态,最终状态又流向界面,界面通过消费状态完成刷新;在这个流动的过程中,若保证了唯一可信数据源,就能实现单向数据流
● 所以 MVI 和 MVP, MVVM 不同,它关心的不是具体的界面状态持有者,而是整个更新界面的数据链路流动方式和方向
尾述
点击 底层源码 即可 免费获取 完整代码 以及 更多学习笔记 、 面试视频
技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面
Android 架构师之路还很漫长,与君共勉
PS:有问题欢迎指正,可以在评论区留下你的建议和感受;
欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下
网友评论