MVI 架构详细说明

6 阅读3分钟

一、MVI 到底是什么?

MVI = Model - View - Intent

它是一种严格单向数据流的架构:

  1. View:页面,只做两件事

    • 展示 UI
    • 发送用户意图(点击、刷新、输入…)
  2. Intent:用户意图 / 页面事件

  3. Model:页面唯一状态(UiState)

  4. 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 架构的数据流(最关键)

  1. 用户点击按钮
  2. View 发送 HomeIntent.LoadData
  3. ViewModel 收到意图
  4. 执行逻辑(网络请求)
  5. 更新 State:copy(loading=true)
  6. 拿到数据后:copy(list=newList, loading=false)
  7. View 自动收到新 State
  8. UI 刷新

全程单向、可追踪、无歧义。

五、MVI 对比 MVVM(一眼看懂区别)

1、 MVVM
  • 多个 LiveData
  • 多个数据源
  • View 可直接改数据
  • 逻辑散乱
  • 容易出现状态不一致
2、 MVI
  • 一个 State
  • 一个数据源
  • View 只能发 Intent
  • 逻辑集中
  • 状态永远一致
  • 可维护性极高
3、 为什么 MVI 比 MVVM 更稳定?
对比点MVVMMVI
数据流方向双向绑定(容易混乱)单向流动(清晰可追踪)
状态管理可能多个来源修改状态集中存储于 State
调试难度中等(需跟踪多处变化)较低(状态链路可还原)
可维护性一般高(每个状态可复现)

六、MVI 适合什么项目?

  • 中大型项目
  • 业务复杂页面
  • 列表、刷新、加载、分页多的页面
  • 团队协作、需要规范的项目
  • 追求高稳定性、低 Bug

现在 Jetpack 官方推荐的就是 MVI 架构。

七、一句话终极总结

MVI 是一种以 “唯一不可变状态” 为核心、严格单向数据流驱动的架构,

让页面状态可预测、可控制、不易出 Bug。

它不是 LiveData 换 StateFlow,而是一套完整的代码组织规范。