MVI 模式及mvp,mvvm对比

一、MVI 模式实现(登录场景)

MVI(Model-View-Intent)核心是单向数据流:View 发送 Intent → Model 处理并生成新 State → View 接收 State 刷新 UI,所有状态变化可追溯、可预测。

核心概念
  • Intent:用户行为(如 "登录请求""清空输入"),是 View 向 Model 发送的唯一指令。
  • State:页面的完整状态(如加载中、登录成功 / 失败、输入框内容),是 View 渲染的唯一依据。
  • Model:处理 Intent,结合数据层逻辑生成新 State。
  • View:仅负责渲染 State、发送 Intent,无任何业务逻辑。
完整代码实现
1. 配置基础依赖(协程 + LiveData)

gradle

复制代码
dependencies {
    // 协程
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
    // ViewModel + LiveData
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
}
2. 定义 Intent(用户行为)

kotlin

复制代码
// LoginIntent.kt
sealed class LoginIntent {
    // 登录请求Intent(携带用户名/密码)
    data class LoginRequest(val username: String, val password: String) : LoginIntent()
    // 清空输入Intent
    object ClearInput : LoginIntent()
}
3. 定义 State(页面状态)

kotlin

复制代码
// LoginState.kt
sealed class LoginState {
    // 初始状态
    object Idle : LoginState()
    // 加载中状态
    object Loading : LoginState()
    // 登录成功状态
    data class LoginSuccess(val msg: String) : LoginState()
    // 登录失败状态
    data class LoginFailure(val errorMsg: String) : LoginState()
    // 输入内容变化状态
    data class InputChanged(val username: String, val password: String) : LoginState()
}
4. Model 层(数据处理,复用逻辑)

kotlin

复制代码
// LoginRepository.kt(替代原Model,更贴合MVI命名)
class LoginRepository {
    suspend fun login(username: String, password: String): String {
        // 模拟异步请求(协程IO线程)
        return withContext(Dispatchers.IO) {
            delay(1000)
            when {
                username.isBlank() -> "用户名不能为空"
                password.isBlank() -> "密码不能为空"
                username == "admin" && password == "123456" -> "登录成功!"
                else -> "用户名或密码错误"
            }
        }
    }
}
5. ViewModel 层(核心:处理 Intent,生成 State)

kotlin

复制代码
// LoginViewModel.kt
class LoginViewModel : ViewModel() {
    // 接收Intent的管道(Channel:协程安全的消息传递)
    private val intentChannel = Channel<LoginIntent>(Channel.UNLIMITED)
    // 页面状态(LiveData:通知View刷新)
    private val _state = MutableLiveData<LoginState>(LoginState.Idle)
    val state: LiveData<LoginState> = _state

    private val repository = LoginRepository()
    private var currentUsername = ""
    private var currentPassword = ""

    init {
        // 监听Intent并处理
        handleIntent()
    }

    // 接收View发送的Intent
    fun sendIntent(intent: LoginIntent) {
        viewModelScope.launch {
            intentChannel.send(intent)
        }
    }

    // 处理Intent的核心逻辑
    private fun handleIntent() {
        viewModelScope.launch {
            intentChannel.consumeAsFlow().collect { intent ->
                when (intent) {
                    is LoginIntent.LoginRequest -> handleLogin(intent.username, intent.password)
                    LoginIntent.ClearInput -> {
                        currentUsername = ""
                        currentPassword = ""
                        _state.postValue(LoginState.InputChanged("", ""))
                    }
                }
            }
        }
    }

    // 处理登录逻辑
    private suspend fun handleLogin(username: String, password: String) {
        // 更新当前输入状态
        currentUsername = username
        currentPassword = password
        _state.postValue(LoginState.Loading)

        // 调用Repository获取结果
        val result = repository.login(username, password)
        // 生成最终State
        val newState = if (result == "登录成功!") {
            LoginState.LoginSuccess(result)
        } else {
            LoginState.LoginFailure(result)
        }
        _state.postValue(newState)
    }
}
6. View 层(Activity + 布局)

kotlin

复制代码
// LoginActivity.kt
class LoginActivity : AppCompatActivity() {
    private lateinit var viewModel: LoginViewModel
    private lateinit var etUsername: EditText
    private lateinit var etPassword: EditText
    private lateinit var btnLogin: Button
    private lateinit var progressDialog: ProgressDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        // 初始化控件
        etUsername = findViewById(R.id.et_username)
        etPassword = findViewById(R.id.et_password)
        btnLogin = findViewById(R.id.btn_login)
        progressDialog = ProgressDialog(this).apply {
            setMessage("登录中...")
            setCancelable(false)
        }

        // 初始化ViewModel
        viewModel = ViewModelProvider(this)[LoginViewModel::class.java]

        // 监听State变化,刷新UI
        observeState()

        // 登录按钮点击:发送LoginRequest Intent
        btnLogin.setOnClickListener {
            val username = etUsername.text.toString().trim()
            val password = etPassword.text.toString().trim()
            viewModel.sendIntent(LoginIntent.LoginRequest(username, password))
        }

        // 输入框变化:模拟发送InputChanged(简化版,实际可封装)
        etUsername.addTextChangedListener {
            viewModel.sendIntent(LoginIntent.ClearInput) // 示例:仅演示Intent发送
        }
    }

    // 核心:观察State并更新UI
    private fun observeState() {
        viewModel.state.observe(this) { state ->
            when (state) {
                LoginState.Idle -> {}
                LoginState.Loading -> progressDialog.show()
                is LoginState.LoginSuccess -> {
                    progressDialog.dismiss()
                    Toast.makeText(this, state.msg, Toast.LENGTH_SHORT).show()
                    startActivity(Intent(this, MainActivity::class.java))
                    finish()
                }
                is LoginState.LoginFailure -> {
                    progressDialog.dismiss()
                    Toast.makeText(this, state.errorMsg, Toast.LENGTH_SHORT).show()
                }
                is LoginState.InputChanged -> {
                    etUsername.setText(state.username)
                    etPassword.setText(state.password)
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        progressDialog.dismiss()
    }
}
7. 布局文件(activity_login.xml)

xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="20dp">

    <EditText
        android:id="@+id/et_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        android:inputType="text"
        android:maxLines="1"/>

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:hint="请输入密码"
        android:inputType="textPassword"
        android:maxLines="1"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="登录"/>

</LinearLayout>

二、MVP/MVVM/MVI 核心对比

维度 MVP(Model-View-Presenter) MVVM(Model-View-ViewModel) MVI(Model-View-Intent)
核心思想 中间层解耦:Presenter 协调 View 和 Model 数据驱动:ViewModel 持有 LiveData,DataBinding 绑定 UI 单向数据流:Intent→Model→State→View,状态唯一且可预测
数据流 双向(View→Presenter→Model,Model→Presenter→View) 双向(DataBinding 双向绑定)+ 单向(LiveData 推送) 严格单向(Intent→State→View)
层职责 - View:UI 展示 + 交互- Presenter:业务逻辑 + 线程切换- Model:数据处理 - View:UI 展示 + 观察 LiveData- ViewModel:业务逻辑 + 持有状态- Model:数据处理 - View:发送 Intent + 渲染 State- Model:处理 Intent 生成 State- Intent:用户行为封装- State:页面完整状态
状态管理 分散(Presenter 临时持有,易不一致) 集中(ViewModel 持有 LiveData,但可多数据源修改) 唯一可信源(State 是页面唯一状态,不可变)
生命周期 Presenter 需手动管理(弱引用 + detach),易内存泄漏 ViewModel 由系统管理,生命周期独立于 Activity ViewModel 管理,State 全量存储,旋转不丢失
可测试性 中等(Presenter 可 Mock View,但需处理线程) 高(ViewModel 无 View 引用,可独立测试) 极高(State/Intent 纯数据类,逻辑无副作用)
复杂度 低 - 中等(易上手,适合小项目) 中等(DataBinding/LiveData 有学习成本) 高(需封装 Intent/State,适合复杂页面)
适用场景 小型项目、快速迭代、团队技术栈简单 中大型项目、数据驱动 UI、Jetpack 生态 复杂页面(如表单 / 电商)、状态需追溯 / 回滚、团队规范高
核心问题 Presenter 易膨胀,状态分散导致 UI 不一致 双向绑定调试困难,状态修改入口多 模板代码多,简单页面 "过度设计"

三、选型建议

  1. MVP:适合新手入门、小型项目或维护老项目,核心优势是 "简单易理解",但需注意 Presenter 内存泄漏问题。
  2. MVVM:Android 官方推荐,中大型项目首选,结合 Jetpack(ViewModel/LiveData/Compose)可大幅提升开发效率,缺点是双向绑定调试稍复杂。
  3. MVI:适合状态复杂的场景(如金融 / 电商表单、多状态切换页面),核心优势是 "状态可预测、可追溯",但需额外封装 Intent/State,小项目不建议使用。

总结:MVI 是 MVVM 的 "强化版",通过单向数据流解决了 MVVM 状态分散的问题,但学习和开发成本更高;MVP 是最基础的解耦模式,适合快速落地。实际项目中可混合使用(如简单页面用 MVP/MVVM,复杂页面用 MVI)。

相关推荐
非情剑2 小时前
Java-Executor线程池配置-案例2
android·java·开发语言
JienDa2 小时前
JienDa聊PHP:PHP 8革命性特性深度实战报告:枚举、联合类型与Attributes的工程化实践
android·开发语言·php
向葭奔赴♡2 小时前
Android AlertDialog实战:5种常用对话框实现
android·java·开发语言·贪心算法·gitee
峥嵘life2 小时前
Android EDLA 搭建Linux测试环境简介
android·linux·运维
李坤林3 小时前
Anroid Perfetto
android
2501_915909063 小时前
深度解析 iOS 内存占用,构建多工具协同的内存诊断、监控与优化体系
android·ios·小程序·https·uni-app·iphone·webview
sang_xb3 小时前
深入解析 HashMap:从存储架构到性能优化
android·java·性能优化·架构
啃火龙果的兔子4 小时前
android studio运行下载gradle特别慢怎么处理
android·ide·android studio