MVC
Model View Controller 由三部分组成 (M-V-C):
Model:数据模型,管理数据状态(Bean、Entity)
View:可视化界面,用于展示数据
Controller:控制者,负责处理用户与应用之间的交互,包括业务逻辑;

MVC中,View和Model可以直接通信,增加了其耦合!(缺点)
Android 中, XML 编写的界面就是 View 层,但是 XML 对 UI 的控制太差(只是用来声明界面长啥样,而不能控制界面做什么),因此作为 XML 界面的承载体 Acitivty / Fragment 就需要承载 View 的更新控制职责;
Acitivity/Fragment 既作为 Controller 层也作为 View 层:
Activity 需要根据用户的输入事件去执行相关操作;
比如点击按钮发送网络请求就需要:
Button.setOnClickLisenter(作为 View 层响应用户输入)在其内部调用
fetchNetworkData(作为 Controller 层通知 Model 层更新数据)根据拿到的数据更新 UI 界面
TextView.setText(xxx)(作为 View 层更新界面)
如果开发不规范,甚至可能在 Activity 中编写 Model 层处理数据的操作(比如网络请求拿到数据后转成VO的操作),就会导致三层都集中在 Activity 中,代码骤增,其耦合性也是非常高的;
Android实现 MVC 的大致架构如下:
Activity 既作为 Controller 也作为 View 层:在 Activity 中,同时存在访问 Model 层、更新UI的代码

MVP
Model-View-Presenter 由三部分组成:
Model:数据模型,管理数据状态;
View:可视化界面,负责数据的展示和与用户的交互;
Presenter:主持者,Presenter 通过 View 接收用户的输入,通知 Model 层处理数据并将处理结果传递回 View;
相较于 MVC 架构模式,MVP 架构模式最大的变化就是将 View 和 Model 的依赖关系删除,让 Presenter 作为 View 和 Model 之间的桥梁:
Presenter需要接收 View 传递的事件并进行处理后,通知 Model 更新数据
Model 更新完毕数据后,通知 Presenter 更新完毕,Presenter 通知 View 层更新;
缺点:
需要定义大量的接口规定 View 层和 Model 层的方法,会引入大量的文件;同时随着业务逻辑的增多,需要更多的接口进行回调;
因为 Persenter 作为 View层 和 Model 层的中转站,所以 Persenter 需要持有 View 层(Activity)和Model层的引用,如果处理不当(生命周期)会导致内存泄漏;

Android 的 MVP:
单独实现 XxxxPresenter,内部带有对 Acitivty (View层)的引用
class XxxxPresenter: IXxxPresenter { private val xxxModel: IXxxModel // Model 层的引用 private val xxxView: IXxxView // View 层的引用 // 对 xxxModel 的调用就是处理数据 // 对 xxxView 的调用就是通知 View 更新 }Activity 主要负责 View 层的更新
class MainActivity: AppCompatActivity(), IXxxView { private val xxxPresenter: XxxPresenter // 与 Presenter 层交互 }Model 层负责数据的处理和通知:
class XxxModel: IXxxModel { // 更新数据,更新完数据后,还需要通知 Presenter private val presenter: IxxxPresenter }
与 MVC 最大的变化就是:
Activity 不再担任 Controller,而是将其独立出去,变成为 Presenter 层;让 Activity 专于 View 的更新操作;

MVVM
由三个部分组成 (M-V-VM):
Model(模型): 表示应用程序的数据模型或业务逻辑,负责存储数据的存储、处理和操作(数据结构、数据库操作和网络请求等)
View(视图):可视化界面,用于展示数据并与用户进行交互;
ViewModel(视图模型):
是 View 和 Model 之间的桥梁,负责将数据从 Model 中取出并转换成 View 可用的形式
并不直接操作UI,而是通过数据绑定机制与 View 进行绑定,使得数据更新时让 UI 自动同步;
ViewModel 也包含用户交互的逻辑(比如处理用户输入、点击事件等)

主要目标是将应用的UI与其底层数据模型分离,通过数据绑定实现数据和UI的自动同步,从而降低代码的耦合度;
View 负责与 ViewModel 中的数据绑定, 进行UI的更新,并将事件通知给 ViewModel;
ViewModel 负责处理 View 的事件,并通知 Model 更新数据,Model更新数据后通知给 ViewModel, ViewModel 更新对应的数据,因为数据绑定,View层自动更新UI;
上面的架构图似乎和 MVP 相同,但是具体实现不同:
MVP是通过接口回调的形式,被动的接收 Persenter 层传递的数据;
MVVM是以观察者的形式,通过观察数据变化来主动更新UI;
MVVM 的核心:
数据绑定: 将 View 和 ViewModel 的数据同步连接,当 ViewModel 的数据发生变化时,数据绑定会自动更新 View 绑定这些数据的部分;
双向数据绑定:数据的更新实时反映到UI上,UI对数据的操作也能更新 ViewModel 的数据;
在 Google 的曾经推荐过的架构如下图:

ViewModel 负责界面数据的存储、同步、更新操作,同时负责访问 Model 的数据等业务逻辑;
View 主要是 Activity / Fragment 所呈现的 UI 界面,使用 DataBinding/LiveData/StateFlow 等支持观察者模式的数据结构来实现数据绑定;
Model 中统一由 Repository 层负责所有数据的获取和更新,数据的来源又分为本地数据库和远程服务器拉取的数据;
MVI
现在谷歌目前所推荐的架构(Now In Android 用的是这个架构);
M-V-I由三部分组成:
Model:和先前的架构都不同,这里指的是界面状态,比如页面加载状态、组件可用状态等,也负责数据处理;
View:可视化界面,可以接收 Model的状态变化,通过 Intent 事件对界面进行变化;
Intent:(不是系统的Intent)将用户的操作包装为 Intent 事件,交由 Model 层进行处理
我个人认为类似 有限状态机,
将界面划分为多个State,将操作划分为多个Intent;
Intent可以触发 State 的变换,形成完整的状态流转;

Android 代码大致实现:
定义界面状态,这里的ApiResponseState为获取API数据相关的界面状态:
// 使用 Kotlin 提供的 sealed 关键字能够很好的适配这种有限状态; sealed interface ApiResponseState<out T> { data object BeforeRequest: ApiResponseState<Nothing> data object Loading : ApiResponseState<Nothing> data class NetworkError(val errMsg: String, val errCode: String) : ApiResponseState<Nothing> data class Success<T>(val data: T) : ApiResponseState<T> }定义 ViewModel,用于处理数据以及更换状态:
class ExampleViewModel: ViewModel { private val _uiState = MutableStateFlow<ApiResponseState<InfoBean>>(ApiResponseState.BeforeRequest) val uiState = _uiState.asStateFlow() private fun fetchInfoBean() { // 界面进入加载状态 _uiState = ApiResponseState.Loading viewModelScope.launch(Dispatchers.IO) { runCatching { repository.getInfoBean() }.onSuccess { // 界面进入加载成功状态 _uiState.value = ApiResponseState.Success(it) }.onError { // 界面进入加载失败状态 _uiState.value = ApiResponseState.NetworError(it.msg, /* custom code */) } } } }根据用户的操作,划分为多个Intent,类似 State
sealed interface UserIntent { data object StartRequest: UserIntent data object Refreshing: UserIntent // .... }然后在 ViewModel 中根据事件来调用对应的方法,进行数据处理
fun dispatchUserIntent(intent: UserIntent) { when(intent) { StartRequest -> { fetchInfoBean() } Refrehsing -> { refreshInfoBean() } // .... } }最后在 View 层监听 Model 状态的变化,并执行对应的 UI 更新,以及响应用户的事件并交由ViewModel处理
lifecycleScope.launch { viewModel.uiState.collect { state -> when(state) { ApiResponseState.Loading -> { // 加载状态对应的UI } is ApiResponseState.Success -> { state.data // 数据 // 加载成功对应的UI } ApiResponseState.Network -> { // 加载失败对应的UI } } } } // 响应用户输入的事件 viewBinding.refreshingButton.setOnClickLisenter { // 执行刷新操作 viewModel.dispatchUserIntent(UserIntent.Refreshing) }
MVI 适合 Compose
对于上面的 State 来说,更多的情况是数据某一部分发生变化(比如 copy 修改某一部分的值),因为 Compose 支持差量更新(仅支持数据发生变化的部分);
但是对于传统的 View 系统来说:
lifecycleScope.launch {
viewModel.uiState.collect { state ->
// 更新所有与 state 相关的UI控件
}
}每次 State 发生更新时,都会更新与其相关的UI控件,出现上面的数据某一部分发生变化的场景,这种写法会导致数据没有发生变化,但是发生刷新的问题,造成刷新过度的问题;
如果需要避免这个问题,就需要独立做差量更新(比如判断与先前的值是否相同的操作),而 Compose 框架本身就带有差量更新,就不用手动编写这部分代码;
UseCase
谷歌所推荐的应用架构中,包含了几个原则:
分离关注点
数据模型驱动UI更新
单一数据源SSOT:数据只有一处来源,数据的获取、更新都只能从来源处执行,或者说相关操作由来源处提供;
单向数据流 UDF:数据状态仅从一个方向流动(往UI层),而修改数据的事件往反方向流动
因此将应用划分为:
UI层:Activity/Compose + ViewModel(存State)
Domain 层:可选,Data 层到 UI 层的数据处理逻辑
Data 层:数据源,负责数据加载、更新、持久化;
相较于之前的 MVVM 架构中的 ViewModel 需要包含大量的业务逻辑和处理 View 层的事件输入代码,Domain 层将 ViewModel 中复杂的业务逻辑或者重复使用的业务逻辑抽离封装,减少 ViewModel 的职责;
在 Domain 层中,推荐使用 UseCase 作为实现方式:
不持有UI状态:为了保证 SSOT 和 UDF,不需要根据状态来调整自身逻辑;
单一职责:一个 UseCase 仅做一件事;
大致代码:
class GetInfoBeanUseCase constructor(
private val dataRepository: DataRepository
) {
operator fun invoke(/* 所需要的数据 */): Flow<InfoBean> {
return dataRepository.getInfoBeanCase(...).map{ /* 负责处理获取到的数据 */}
/* 将这些业务逻辑从 ViewModel 抽离出来 */
}
}