一、MVI 到底是什么?
MVI = Model - View - Intent
它是一种严格单向数据流的架构:
-
View:页面,只做两件事
- 展示 UI
- 发送用户意图(点击、刷新、输入…)
-
Intent:用户意图 / 页面事件
-
Model:页面唯一状态(UiState)
-
ViewModel:中间处理者
- 接收 Intent
- 处理逻辑
- 更新 State
- 推送给 View
核心规则(必须记住)
- 整个页面只有一个 UiState
- UI 只能观察 State,不能修改 State
- 所有操作必须通过 Intent 发送
- State 是不可变的,只能 copy 更新
- 数据流永远单向: View → Intent → ViewModel → Model → View
二、MVI 标准结构(固定 4 件套)
任何一个 MVI 页面,一定包含这 4 个类:
1. UiState —— 页面所有状态(唯一数据源,这是MVI的核心思想)
页面上能看到的一切,都在这里。
kotlin
data class HomeUiState(
val list: List<String> = emptyList(),
val loading: Boolean = false,
val error: String? = null,
val refreshEnable: Boolean = false
)
2. UiIntent —— 用户能做的所有操作
kotlin
sealed class HomeIntent {
object LoadData : HomeIntent() // 加载数据
object RefreshData : HomeIntent() // 下拉刷新
data class ItemClick(val position: Int) : HomeIntent() // 点击item
}
3. ViewModel —— 处理意图、更新状态
kotlin
class HomeViewModel : ViewModel() {
// 1. 唯一状态
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
// 2. 接收意图
fun dispatch(intent: HomeIntent) {
when (intent) {
is HomeIntent.LoadData -> loadData()
is HomeIntent.RefreshData -> refreshData()
is HomeIntent.ItemClick -> itemClick(intent.position)
}
}
// 3. 业务逻辑 → 更新状态
private fun loadData() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(loading = true)
// 模拟网络请求
delay(1000)
val newData = listOf("A", "B", "C")
_uiState.value = _uiState.value.copy(
list = newData,
loading = false
)
}
}
private fun refreshData() { ... }
private fun itemClick(pos: Int) { ... }
}
4. View(Activity/Fragment)
只做两件事:
- 发 Intent
- 收 State → 刷新 UI
kotlin
lifecycleScope.launch {
vm.uiState.collect { state ->
// 统一更新 UI
tv.text = state.list.toString()
loading.visibility = if (state.loading) VISIBLE else GONE
}
}
// 发送意图
btn.setOnClickListener {
vm.dispatch(HomeIntent.LoadData)
}
三、MVI 架构的核心特点(真正灵魂)
1. 唯一可信数据源(SingleSourceOfTruth)
整个页面只有一个 UiState不会出现多个 LiveData 不同步的问题。
2. 严格单向数据流
View 不能直接改数据ViewModel 不能直接操作 View数据流永远是一条直线:
Intent → ViewModel → State → View
3. 状态不可变(Immutable)
State 必须是 data class,只能 copy
kotlin
// 正确
_uiState.value = _uiState.value.copy(list = newList)
// 错误(不允许直接修改)
_uiState.value.list = newList
好处:
- 线程安全
- 无副作用
- 可追溯、可回放、可测试
4. 所有行为可枚举
用户能干嘛?看 HomeIntent 就知道。代码可读性极高。
5. UI 完全被动
UI = 状态的映射UI = f(State)
状态变 → UI 自动变不会出现 UI 不同步、错乱、闪烁。
四、MVI 架构的数据流(最关键)
- 用户点击按钮
- View 发送
HomeIntent.LoadData - ViewModel 收到意图
- 执行逻辑(网络请求)
- 更新 State:
copy(loading=true) - 拿到数据后:
copy(list=newList, loading=false) - View 自动收到新 State
- UI 刷新
全程单向、可追踪、无歧义。
五、MVI 对比 MVVM(一眼看懂区别)
1、 MVVM
- 多个 LiveData
- 多个数据源
- View 可直接改数据
- 逻辑散乱
- 容易出现状态不一致
2、 MVI
- 一个 State
- 一个数据源
- View 只能发 Intent
- 逻辑集中
- 状态永远一致
- 可维护性极高
3、 为什么 MVI 比 MVVM 更稳定?
| 对比点 | MVVM | MVI |
|---|---|---|
| 数据流方向 | 双向绑定(容易混乱) | 单向流动(清晰可追踪) |
| 状态管理 | 可能多个来源修改 | 状态集中存储于 State |
| 调试难度 | 中等(需跟踪多处变化) | 较低(状态链路可还原) |
| 可维护性 | 一般 | 高(每个状态可复现) |
六、MVI 适合什么项目?
- 中大型项目
- 业务复杂页面
- 列表、刷新、加载、分页多的页面
- 团队协作、需要规范的项目
- 追求高稳定性、低 Bug
现在 Jetpack 官方推荐的就是 MVI 架构。
七、一句话终极总结
MVI 是一种以 “唯一不可变状态” 为核心、严格单向数据流驱动的架构,
让页面状态可预测、可控制、不易出 Bug。
它不是 LiveData 换 StateFlow,而是一套完整的代码组织规范。