MVC、MVP 与 MVVM:Android 架构演进之路

MVC、MVP 与 MVVM:Android 架构演进之路

文章目录

引言:从“能跑就行”到“优雅可维”——架构即工程文明

站在 2025 年回望,Android 开发已走过近二十年。从早期“Activity 即一切”的野蛮生长,到如今以 Jetpack Compose + Kotlin Coroutines + Clean Architecture 为核心的现代开发范式,应用架构的演进不仅是技术的升级,更是工程思维的成熟。

在这条演进之路上,MVC、MVP、MVVM 三大模式如同三座里程碑,分别代表了 Android 社区在不同阶段对关注点分离(Separation of Concerns)可测试性(Testability)状态管理(State Management) 的探索与突破。今天,我们不仅回顾它们“是什么”,更要理解它们“为何而来”、“因何而去”,以及它们如何共同塑造了今日 Android 开发的底层逻辑。

第一章:混沌之初 —— “上帝类”的技术债深渊

在架构意识尚未觉醒的年代,一个典型的 MainActivity 往往集万千职责于一身:

  • 通过 findViewById 操作 UI 控件;
  • 直接发起 OkHttp 网络请求;
  • 使用 Room 或 SQLiteOpenHelper 操作数据库;
  • onCreate() 中处理业务逻辑分支;
  • onActivityResult() 中解析 Intent 数据;
  • 甚至内嵌 AsyncTask 处理异步任务……

这种“上帝类”模式看似高效,实则埋下巨大隐患:

问题维度具体表现
高耦合UI、网络、DB、业务逻辑全部交织,修改一处可能引发连锁崩溃
不可测试业务逻辑强依赖 ContextActivity 等 Android SDK 类型,无法脱离设备运行单元测试
状态脆弱屏幕旋转导致 Activity 重建,未妥善保存的数据(如网络加载中的状态)瞬间丢失
维护地狱单文件超 2000 行代码,新人接手需数周才能理清逻辑

正是这种“开发快、维护慢、测试难”的恶性循环,催生了对架构模式的迫切需求。而第一个被引入的,便是软件工程的经典范式——MVC

第二章:MVC(Model-View-Controller)——理想很丰满,现实很骨感

1. 理论模型 vs Android 实现

MVC 的核心在于三者职责分离:

  • Model:封装数据与业务规则(如 User、Repository)。
  • View:纯展示层(XML 布局)。
  • Controller:协调输入与输出(接收点击 → 调用 Model → 更新 View)。

但在 Android 中,没有独立的 ControllerActivity / Fragment 被迫同时承担 View(UI 渲染)Controller(事件处理) 双重角色:

 +------------------+ | Model | ←→ (数据) +------------------+ ↑ | (调用/回调) +-------------------------------+ | Activity/Fragment (V + C) | | - setContentView() | | - findViewById() | | - onClick() { | | model.fetchData(); | | updateUI(); | | } | +-------------------------------+ ↑ | (布局引用) +------------------+ | XML Layout | ← (View) +------------------+ 

2.MVC 的历史贡献与局限

贡献

  • 首次将“分层”思想带入 Android,打破“上帝类”垄断。
  • 推动开发者思考“数据”与“界面”的边界。

局限

  • 角色混淆Activity 既是 View 又是 Controller,解耦不彻底。
  • 测试瓶颈:关键逻辑仍在 Activity 中,无法进行纯 JVM 单元测试。
  • 生命周期耦合:Model 若持有 Activity 引用,极易内存泄漏。
2025 年回看:MVC 更像是一次“思想启蒙”,而非实用方案。它暴露了 Android 框架设计与经典 MVC 的天然冲突,为 MVP 的登场埋下伏笔。

第三章:MVP(Model-View-Presenter)——解耦的极致追求

MVP 通过引入 Presenter 彻底切断 View 与 Model 的直接联系,实现“被动视图”理念。

1. 核心组件与交互流

  • View:由 Activity / Fragment 实现的接口,仅暴露 UI 操作方法(如 showLoading()updateList())。
  • Presenter:纯 Kotlin/Java 类,持有 View 接口引用,负责所有业务逻辑。
  • Model:数据源(Repository、API、DB)。

View(Activity)PresenterModel(Repo)onUserClick()fetchData()return datashowData(data)View(Activity)PresenterModel(Repo)

2.MVP 的革命性优势

  1. 彻底解耦:View 与 Model 零依赖,Presenter 成为唯一中介。
  2. 高可测试性:Presenter 可在 JVM 上通过 Mockito Mock View 和 Model 进行完整单元测试。
  3. 职责清晰:View 只负责“显示什么”,Presenter 决定“显示什么”。

3.MVP 的沉重代价

  1. 生命周期陷阱
    • Presenter 持有 View 引用 → 屏幕旋转时若未解绑,导致内存泄漏。
    • 需手动实现 onSaveInstanceState / onRestoreInstanceState 保存 Presenter 状态。
  2. Presenter 肿胀:复杂页面的所有逻辑涌入 Presenter,演变为“新上帝类”。

样板代码爆炸

interface MainContract {interface View {funshowUsers(users: List<User>)funshowError(msg: String)}interface Presenter {funloadUsers()}}

每个页面需定义 Contract 接口,Presenter 与 View 方法一一对应。

2025 年反思:MVP 是“为了解耦而解耦”的典型。它解决了 MVC 的痛点,却引入了新的复杂性。其兴盛(约 2016–2019)恰逢 Android 单元测试文化兴起,但最终被更优雅的 MVVM 取代。

第四章:MVVM(Model-View-ViewModel)——数据驱动的现代范式

MVVM 的崛起,离不开 Google Jetpack 的强力推动。它不再依赖“接口回调”,而是通过 可观察数据 + 生命周期感知 实现自动同步。

1.核心组件与响应式流

  • ViewModel:继承 androidx.lifecycle.ViewModel,持有 LiveData / StateFlow 封装的 UI 状态。
  • ViewActivity / Fragment 通过 observe() 监听状态变化,自动更新 UI。
  • Model:通常由 Repository 统一管理多数据源(网络 + 本地缓存)。
class MainViewModel :ViewModel(){privateval _users = MutableLiveData<List<User>>()val users: LiveData<List<User>>= _users funloadUsers(){ viewModelScope.launch{ _users.value = repository.getUsers()}}}// Activity 中 viewModel.users.observe(this){ users -> adapter.submitList(users)}

2.MVVM 的四大支柱优势

  1. 声明式 UI 更新:数据变 → UI 自动变,无需手动调用 setText() / notifyDataSetChanged()
  2. 生命周期安全LiveData 仅在 LifecycleOwner(如 Activity)处于 STARTED/RESUMED 时通知,避免空指针与内存泄漏。
  3. 配置变更无忧ViewModelViewModelProvider 管理,在屏幕旋转等场景下自动保留实例。
  4. 测试友好ViewModel 不依赖 Android Context,可轻松单元测试。

3.MVVM 的挑战与应对

  • 调试难度:数据流隐式传递,错误堆栈不直观。
    → 解决方案:使用 Kotlin Flow + StateFlow 提供更清晰的调试信息;配合 Compose Preview 实时预览状态。
  • 过度依赖绑定:滥用 Data Binding 可能导致 XML 逻辑膨胀。
    → 最佳实践:View Binding + 手动 observe 成为主流,平衡简洁性与可控性。
2025 年现状:MVVM 已成为 Android 官方推荐架构的核心,尤其与 Jetpack Compose 结合后,形成“状态驱动 UI”的终极形态。

第五章:超越 MVVM —— 2025 年的架构新范式

MVVM 并非终点。随着应用复杂度提升与声明式 UI 的普及,新一代架构思想正在融合演进:

1. MVI(Model-View-Intent):单向数据流的胜利

MVI 将用户操作抽象为 Intent,状态变更通过 Reducer 生成新 State,形成严格单向流:

sealedclass MainIntent {object LoadUsers :MainIntent()}dataclassMainState(val isLoading: Boolean =false,val users: List<User>=emptyList(),val error: String?=null)class MainViewModel :ViewModel(){privateval _state =MutableStateFlow(MainState())val state: StateFlow<MainState>= _state.asStateFlow()funprocessIntent(intent: MainIntent){when(intent){is MainIntent.LoadUsers ->loadUsers()}}privatefunloadUsers(){// 更新 state via _state.update { ... }}}

优势:状态可预测、可回溯、易于时间旅行调试(Time Travel Debugging)。
契合 Compose:Compose 的 @Composable 函数天然适配“State → UI”映射。

2. 分层架构(Layered Architecture):Google 官方推荐

Google 在 Guide to app architecture 中明确推荐三层架构:

┌──────────────┐ │ UI Layer │ ← ViewModel + Compose/ViewBinding ├──────────────┤ │ Domain Layer │ ← UseCase (封装复杂业务) ├──────────────┤ │ Data Layer │ ← Repository + DataSource (Retrofit, Room) └──────────────┘ 
  • ViewModel 不再直接调用 Repository,而是通过 UseCase(领域层)间接访问。
  • 实现 业务逻辑与数据获取的进一步解耦,提升复用性与可测试性。

3. Kotlin 协程与 Flow:异步编程的基石

  • LiveData 逐渐被 StateFlow / SharedFlow 取代,因其支持冷流、背压、组合操作符。
  • ViewModel.viewModelScope 成为协程默认作用域,自动取消未完成任务。
  • Repository 层全面拥抱 suspend 函数,简化异步链路。

总结:架构演进的本质是“状态管理”的进化

维度MVCMVPMVVMMVI(2025)
状态持有者ActivityPresenterViewModelStateFlow
UI 更新方式手动调用回调接口观察数据响应状态
生命周期管理手动手动(易错)自动自动
可测试性极好极好
核心哲学分离职责彻底解耦数据驱动单向数据流
架构没有银弹,只有权衡(Trade-offs)
MVC 教会我们“分层”,MVP 教会我们“解耦”,MVVM 教会我们“响应式”,MVI 教会我们“状态即真理”。

站在 2025 年,我们不再争论“用 MVP 还是 MVVM”,而是思考:

  • 如何设计不可变的状态模型
  • 如何构建可组合的 UseCase
  • 如何利用 Compose + Coroutines + Flow 实现声明式、响应式、可测试的现代 Android 应用?

而这一切的起点,正是当年那个试图从“上帝 Activity”中挣脱出来的你。

结语
理解 MVC、MVP、MVVM,不是为了怀旧,而是为了看清——
所有架构的本质,都是对“复杂性”的驯服。
愿你在未来的代码中,始终握紧这把名为“关注点分离”的罗盘。

Read more

从千毫秒到亚毫秒:连接条件下推如何让复杂 SQL 飞起来

从千毫秒到亚毫秒:连接条件下推如何让复杂 SQL 飞起来

文章目录 * 前言 * 一、问题背景 * 1.1 客户场景中的典型痛点 * 1.2 业界普遍面临的两大难点 * 1.2.1 语义安全性(Equivalence) * 1.2.2 代价评估(Cost) * 二、传统方案的局限 * 三、金仓数据库基于代价的连接条件下推设计 * 3.1 能不能推:等价性判定(Equivalence) * 3.2 值不值推:代价模型(Cost) * 四、效果验证 * 4.1 最小化用例 * 4.2 复杂场景验证 * 五、总结 前言 在真实的业务系统中,SQL 往往远比教科书示例复杂。随着业务逻辑的不断演进,CTE、

By Ne0inhk
【SpringBoot】统一功能处理详解

【SpringBoot】统一功能处理详解

🎬 那我掉的头发算什么:个人主页 🔥 个人专栏: 《javaSE》《数据结构》《数据库》《javaEE》 ⛺️待到苦尽甘来日 文章目录 * 统一数据返回格式 * 快速入门 * 存在问题 * 代码优化 * 优点 * 统一异常处理 统一数据返回格式 快速入门 统一的数据返回格式使用 @ControllerAdvice 和 ResponseBodyAdvice 的方式实现@ControllerAdvice 表示控制器通知类添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接口,并在类上添加@ControllerAdvice 注解。 packagecom.hbu.book.responseAdvice;importorg.jspecify.annotations.Nullable;importorg.springframework.core.MethodParameter;importorg.springframework.http.MediaType;importorg.springframew

By Ne0inhk
Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:RocketMQ 顺序消息(全局 / 分区顺序) * 什么是顺序消息? * RocketMQ 顺序消息的工作原理 * 全局顺序 vs 分区顺序 * RocketMQ 顺序消息的核心机制 * 全局顺序消息的实现 * 全局顺序的配置要求 * Java 代码示例:全局顺序消息 * 全局顺序的局限性 * 分区顺序消息的实现 * 分区顺序的设计思路 * Java 代码示例:分区顺序消息 * 分区顺序的关键要点 * 顺序消息的消费机制详解 * ConsumeOrderlyStatus 枚举 * 消费失败的处理机制 * 并发消费 vs 顺序消费

By Ne0inhk
一卡通核心交易平台的国产数据库实践解析:架构、迁移与高可用落地

一卡通核心交易平台的国产数据库实践解析:架构、迁移与高可用落地

文章目录 * 摘要 * 1. 业务与技术挑战拆解 * 2. 总体架构(从数据库边界看) * 3. 数据模型:以“不可变流水”为中心 * 3.1 流水表(交易事实表)建议 * 3.2 账户与余额:把“强一致”收敛到最小 * 4. 高可用与容灾:把“不可用窗口”工程化 * 4.1 同城高可用:主备切换与防脑裂 * 4.2 异地灾备:以“可恢复”为目标设计链路 * 5. 性能与稳定性:把瓶颈消灭在“写路径” * 5.1 连接治理:让资源可控 * 5.2 SQL治理:少做无谓计算

By Ne0inhk