详细解析 MVC/MVP/MVVM/MVI 架构完整教程:从入门到实战部署

详细解析 MVC/MVP/MVVM/MVI 架构完整教程:从入门到实战部署

在 Android 开发中,架构设计的核心目标始终是解耦代码、提升可维护性、降低测试成本,而从最初的 MVC 到如今主流的 MVVM、MVI,每一种架构的诞生都是为了解决前一种架构在实际开发中暴露的痛点。我们下面从核心定义、角色分工、优缺点三个维度全面解析 MVC、MVP、MVVM、MVI 四种架构的区别,并用同一个极简业务场景实现四种架构的核心代码,所有示例基于 Kotlin 编写。

前置说明本次所有示例基于同一个业务场景:

页面包含一个 Button 和一个 TextView,点击 Button 触发业务逻辑,获取一段固定文本,最终将文本展示在 TextView 上。

所有架构复用同一个 Model,仅提供一个获取文本的方法,模拟本地 / 网络数据获取:

// 所有架构共用的Model层:负责数据获取,与架构无关

class DataModel {

// 模拟获取数据(本地/网络)

fun getShowText(): String {

return "架构演示:获取到展示文本"

}

}

布局文件如下:

MVC(Model-View-Controller)MVC 是最早的经典三层架构模式,也是 Android 开发入门最易接触的架构。

特性:将代码分为三层,通过分层职责实现初步解耦。View(布局)和 Model(数据)通过 Controller(Activity/Fragment)中转。

安卓端角色分工

Model:数据层,负责数据获取、处理(如网络请求、数据库操作),无任何视图相关逻辑;View:视图层,对应布局文件(xml)+ Activity/Fragment,负责视图的展示和用户交互(如按钮点击);Controller:控制层,由 Activity/Fragment 兼任,负责连接 View 和 Model,接收 View 的交互事件、调用 Model 获取数据,最终将数据传递给 View 更新展示。具体实现如下

// MVC架构 - Activity(View+Controller)

class MVCActivity : AppCompatActivity() {

private lateinit var tvShow: TextView

// 直接持有Model实例

private val dataModel = DataModel()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_common)

tvShow = findViewById(R.id.tv_show_text)

// View层:处理用户交互(按钮点击)

findViewById(R.id.btn_get_text).setOnClickListener {

// Controller层:接收交互,调用Model获取数据

val text = dataModel.getShowText()

// Controller层:直接更新View层视图

tvShow.text = text

}

}

}

通信方式:View(用户交互)→ Controller → Model(获取数据)→ Controller → View(更新展示),View 和 Model 可直接通信。

点击事件中,Activity 既做了「接收用户交互」的 Controller 工作,又做了「调用 Model」的逻辑处理,还做了「更新 TextView」的 View 工作,三层逻辑混在同一方法中。

MVP(Model-View-Presenter)MVP 是 MVC 的改良版,强调视图与逻辑的分离,解决了 MVC 中 V 和 C 耦合的问题,也是早期 Android 中型项目的主流架构。通过接口(Interface)彻底隔离 View 和 Model。Presenter 通过接口操控 View。

特性:在 MVC 基础上引入 Presenter 作为中间层,彻底切断 View 和 Model 的直接通信,所有交互都必须通过 Presenter 中转。

安卓端角色分工

Model:与 MVC 一致,仅负责数据获取 / 处理,无视图依赖;View:视图层,对应 Activity/Fragment(布局文件仍为视图载体),仅负责视图展示和用户交互,无任何业务逻辑,通过接口向 Presenter 传递事件;Presenter:中间层,是架构的核心,持有 View 接口和 Model 实例,接收 View 的事件、调用 Model 获取数据,最终通过 View 接口通知 View 更新展示。具体实现如下

// View接口 - 仅包含视图更新方法,与具体Activity解耦

interface MVPView {

fun updateShowText(text: String)

}

// Presenter接口 - 仅包含业务逻辑方法

interface MVPPresenter {

fun handleGetText()

}

// Presenter - 核心中间层,持有View接口和Model

class MVPPresenterImpl(private val mvpView: MVPView) : MVPPresenter {

private val dataModel = DataModel()

override fun handleGetText() {

// 调用Model获取数据

val text = dataModel.getShowText()

// 通过View接口更新视图,不直接持有Activity

mvpView.updateShowText(text)

}

}

// Activity实现View接口,持有Presenter实例

class MVPActivity : AppCompatActivity(), MVPView {

private lateinit var tvShow: TextView

// 持有Presenter实例

private lateinit var presenter: MVPPresenter

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_common)

tvShow = findViewById(R.id.tv_show_text)

// 初始化Presenter,将当前View接口实现传入

presenter = MVPPresenterImpl(this)

// 处理按钮点击,调用Presenter的业务方法

findViewById(R.id.btn_get_text).setOnClickListener {

presenter.handleGetText()

}

}

// 实现View接口的更新方法

override fun updateShowText(text: String) {

tvShow.text = text

}

}

通信方式:View(用户交互)→ Presenter → Model(获取数据)→ Presenter → View(更新展示),View 和 Model

Activity 仅做「视图展示 + 调用 Presenter」,Presenter 仅做「调用 Model + 通知 View」,两者通过接口通信,即使替换 Activity 实现,Presenter 也无需任何修改。

⚠️ 踩坑记录:

我在实际项目中遇到过一个问题,这个配置在开发环境正常,但生产环境会报错。

后来发现是因为生产环境的版本不一致导致的。建议大家在部署前一定要检查版本兼容性。

MVVM(Model-View-ViewModel)MVVM 是目前 Android 开发的主流架构(Google 官方推荐),核心设计思想是数据驱动视图,彻底摒弃了 MVP 的接口通信,通过 ViewModel 实现 View 和 Model 的解耦,结合 LiveData/DataBinding 实现视图的自动刷新。

特性:以ViewModel为核心,通过数据绑定让视图和数据双向关联,数据变化自动触发视图更新,视图交互触发数据逻辑,无需手动更新视图。

安卓端角色分工

Model:与前两种架构一致,负责数据获取 / 处理;View:视图层,对应 Activity/Fragment + 布局文件,负责视图展示和用户交互,不持有 Model 引用,仅通过观察 ViewModel 的数据源实现视图更新;ViewModel:核心层,负责业务逻辑处理,持有 Model 实例,暴露可观察的数据源(如 LiveData)给 View,不持有任何 View 引用(与生命周期解耦),由系统统一管理生命周期。具体实现如下

// ViewModel - 架构核心,持有Model,暴露LiveData

class MyViewModel : ViewModel() {

private val dataModel = DataModel()

// 暴露可观察的数据源,View层仅需观察

val showTextLiveData = MutableLiveData()

// 处理业务逻辑的方法,供View层调用

fun getShowText() {

val text = dataModel.getShowText()

// 更新LiveData,View会自动收到通知

showTextLiveData.postValue(text)

}

}

// Activity(View)- 观察LiveData,调用ViewModel方法,无任何业务逻辑

class MVVMActivity : AppCompatActivity() {

private lateinit var tvShow: TextView

// 通过ViewModelProvider获取ViewModel,由系统管理生命周期

private val viewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_common)

tvShow = findViewById(R.id.tv_show_text)

// 观察ViewModel的LiveData,数据变化自动更新视图

viewModel.showTextLiveData.observe(this) { text ->

tvShow.text = text

}

// 处理按钮点击,仅调用ViewModel的方法,无其他逻辑

findViewById(R.id.btn_get_text).setOnClickListener {

viewModel.getShowText()

}

}

}

通信方式:View(用户交互)→ ViewModel → Model(获取数据)→ ViewModel(更新数据源)→ View(自动刷新),基于数据驱动实现,ViewModel 和 View 通过可观察数据源(LiveData) 通信。

Activity 中无任何业务逻辑,仅做「观察数据 + 触发方法」,ViewModel 中无任何视图引用,仅做「处理逻辑 + 更新数据」,数据变化后视图自动刷新,无需手动调用更新方法。

MVI(Model-View-Intent)MVI 是在 MVVM 基础上发展而来的新兴架构,核心设计思想是单向数据流 + 状态驱动,解决了 MVVM 中数据流混乱、多事件导致状态不一致的问题,适合大型项目、团队协作场景。

特性:基于函数式编程思想,将页面的所有用户行为封装为 Intent,所有页面状态封装为 State,整个架构的交互围绕「Intent→State」的单向流转展开,View 仅负责展示 State 和发送 Intent,无其他逻辑。

安卓端角色分工

Model:与前几种架构一致,负责数据获取 / 处理;View:视图层,对应 Activity/Fragment + 布局文件,唯一职责:① 发送 Intent(封装用户交互);② 观察 State 并刷新视图,无任何业务逻辑;ViewModel:核心层,接收 View 发送的 Intent,根据 Intent 类型调用 Model 处理数据,最终将处理结果封装为唯一的 State,通过可观察数据源发送给 View;Intent:密封类,封装所有用户行为(如按钮点击、下拉刷新),是 View 向 ViewModel 传递的唯一载体;State:密封类,封装页面的所有状态(如初始状态、成功状态、失败状态),是 ViewModel 向 View 传递的唯一载体。具体实现如下

// 密封类Intent - 封装页面所有用户行为(本场景仅按钮点击)

sealed class PageIntent {

// 按钮点击的Intent

object GetTextIntent : PageIntent()

}

// 密封类State - 封装页面所有状态(本场景仅初始状态、成功状态)

sealed class PageState {

// 初始状态:页面刚加载,无任何操作

object IdleState : PageState()

// 成功状态:获取文本成功,携带展示数据

data class SuccessState(val showText: String) : PageState()

}

// ViewModel - 接收Intent,处理后发送State

class MviViewModel : ViewModel() {

private val dataModel = DataModel()

// 接收Intent的Flow,供View层发送Intent

private val _intent = MutableSharedFlow()

// 发送State的LiveData,供View层观察State

private val _state = MutableLiveData(PageState.IdleState)

val state: LiveData = _state

init {

// 监听Intent,处理不同的用户行为

handleIntent()

}

// 处理Intent的核心方法

private fun handleIntent() {

viewModelScope.launch {

_intent.collect { intent ->

when (intent) {

// 处理按钮点击的Intent

is PageIntent.GetTextIntent -> {

val text = dataModel.getShowText()

// 处理完成后,发送SuccessState

_state.postValue(PageState.SuccessState(text))

}

}

}

}

}

// 供View层调用,发送Intent

fun sendIntent(intent: PageIntent) {

viewModelScope.launch {

_intent.emit(intent)

}

}

}

// Activity(View)- 仅做两件事:发送Intent、观察State更新视图

class MVIActivity : AppCompatActivity() {

private lateinit var tvShow: TextView

private val mviViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_common)

tvShow = findViewById(R.id.tv_show_text)

// 观察State,根据不同状态更新视图

mviViewModel.state.observe(this) { state ->

when (state) {

is PageState.IdleState -> {} // 初始状态,不做处理

is PageState.SuccessState -> tvShow.text = state.showText // 成功状态,展示文本

}

}

// 处理按钮点击,仅发送GetTextIntent,无其他逻辑

findViewById(R.id.btn_get_text).setOnClickListener {

mviViewModel.sendIntent(PageIntent.GetTextIntent)

}

}

}

通信方式:View(用户交互→封装Intent)→ ViewModel(接收Intent→处理→封装State)→ View(观察State→刷新视图),全程单向数据流,Intent 是 View 到 ViewModel 的唯一入口,State 是 ViewModel 到 View 的唯一出口。

View 层不知道任何业务逻辑,仅通过sendIntent发送用户行为;ViewModel 层不知道任何视图逻辑,仅通过state发送页面状态;所有交互都遵循「View→Intent→ViewModel→State→View」的单向流转,即使后续增加更多交互(如下拉刷新、清空文本),仅需新增 Intent 和 State 即可,数据流始终清晰。

四种架构对比架构本身无 “优劣之分”,只有 “适合与否”,实际开发中需根据需要选择合适的架构

对比维度MVCMVPMVVMMVI架构核心分层职责,Activity 兼任 C接口解耦,Presenter中间层数据驱动,ViewModel + 可观察数据源单向数据流,Intent/State 状态驱动核心组件Model、View、Controller

Model、View、PresenterModel、View、ViewModelModel、View、ViewModel、Intent、State通信方式多层直接通信,耦合较严重接口通信,Presenter中转数据驱动,LiveData / 数据绑定单向通信,Intent 入、State 出数据流特点混乱,可双向、可直接双向单向为主,可实现双向绑定严格单向,全程可追溯优势开发成本低、代码量最少、理解直观职责边界清晰,View层纯粹生命周期安全、生态支持最强、低耦合单一状态源(状态集中管理)、单向数据流适用场景极简项目或快速原型开发中小型项目绝大多数主流应用逻辑高度复杂的单页面

🎀 相关推荐

网易云音乐如何设置卡2铃声
365名品汇推荐码多少

网易云音乐如何设置卡2铃声

📅 01-04 👀 2830
「梦想餐厅」非常满足的「巨型梦想圣代」...
365bet足球实时动画

「梦想餐厅」非常满足的「巨型梦想圣代」...

📅 11-05 👀 1477
必看!新手保姆级选香攻略!附10支高分香水!
365bet足球实时动画

必看!新手保姆级选香攻略!附10支高分香水!

📅 11-12 👀 9820