移动应用架构设计:MVVM实战,我的一次真实项目经验分享
引言:为什么我选择了 MVVM

2023年初,我接手了一个中型的 Android 应用重构项目。这个项目已经运行了两年多,早期采用了 MVP 架构,但随着功能模块越来越多,代码耦合严重、测试困难、维护成本高成了老大难问题。
当时我们团队内部对是否重构展开过激烈讨论。有人主张继续在原有架构上优化,也有人建议直接引入 Jetpack 组件进行重构。最终我们决定尝试使用 MVVM(Model-View-ViewModel) 架构,并结合 Jetpack 的 ViewModel 和 LiveData 来推进重构工作。
在这篇文章里,我想通过这次经历,聊聊我们在移动应用中如何落地 MVVM 架构,特别是在实际开发过程中遇到的一些问题和解决方案。如果你也在考虑或者正在实践 MVVM,相信这些内容能带来一些启发。
项目背景:从 MVP 走向 MVVM

我们的 App 主要面向金融行业用户,提供数据查询、图表分析、实时推送等核心功能。随着产品迭代加速,业务逻辑复杂度陡增,MVP 架构下的 Presenter 层逐渐臃肿,且大量业务逻辑分散在 Activity 和 Fragment 中,导致:
- 页面间通信混乱
- 数据状态管理困难
- 单元测试难以覆盖
- 内存泄漏频发
这些问题严重影响了新功能的上线速度和产品的稳定性。面对这种情况,我们决定进行架构升级,目标是:
- 提升代码可维护性
- 实现 UI 与数据解耦
- 支持快速迭代与高质量交付
- 增强组件复用能力
挑战一:业务逻辑复杂,数据流混乱

重构的第一步,是从 MVP 切换到 MVVM。最开始我们就遇到了一个现实问题:原有的数据处理逻辑混杂在多个类中,不知道该把哪些逻辑放到 ViewModel,哪些留在 Repository?
举个例子,在一个“持仓数据详情页”中,页面需要根据不同的筛选条件展示不同的数据统计结果,同时还需要拉取服务器数据、本地缓存、计算百分比变化等多个操作。
最初的设计是把这些都扔进 ViewModel,导致 ViewModel 类变得庞大臃肿。我们很快意识到这是不可持续的做法。
我们的应对策略
我们参考了官方推荐的架构分层模型,并做了一些本地化调整:
UI -> ViewModel <-> LiveData/StateFlow
↓
Repository
↓
Local Data Source / Remote Data Source
具体来说,我们做了几件事:
- 将所有数据获取封装到
Repository层,保持单职责原则 - ViewModel 只负责协调请求和更新 UI 需要的数据结构(如转换 DTO → ViewData)
- 使用
LiveData或StateFlow管理状态变更并通知 UI 更新 - 把复杂的逻辑判断和数据拼接移到 UseCase 类中统一管理
这样做之后,Activity/Fragment 几乎只负责注册监听事件和绑定数据,大大提升了可读性和可测试性。
挑战二:页面交互复杂,双向绑定不好控制

另一个挑战发生在表单提交、动态 UI 这类场景中。比如一个“创建投资计划”的页面,我们需要根据用户输入不断更新下方的预览数据,包括时间轴、收益率曲线、金额变化等。
最初我们使用 MutableLiveData 手动通知更新,代码量很大而且容易出错。比如:
viewModel.amount.observe(this) {
updatePreview()
}
viewModel.duration.observe(this) {
updatePreview()
}
// ……还有七八个类似的观察者
这种方式不仅重复冗余,还容易漏掉依赖项,导致状态不一致。
解决方案:引入 StateFlow + combine
后来我们改用 Kotlin 的 StateFlow,配合 combine 方法,实现了更优雅的响应式编程:
val inputValid = combine(
viewModel.amount,
viewModel.duration,
viewModel.startDate
) { amount, duration, date ->
amount.isNotBlank() && duration > 0 && date != null
}.launchAndCollectIn(lifecycleScope)

inputValid.launchAndCollectIn(lifecycleScope) {
submitButton.isEnabled = it
}
这种方式不仅逻辑清晰,还能自动处理多个输入间的依赖关系。从此告别手动注册一堆 Observer 的痛苦。
挑战三:适配不同屏幕尺寸,状态恢复成难题
由于我们的 App 需要兼容平板、手机甚至折叠屏设备,页面状态恢复一直是个难点。特别是当用户切换屏幕方向时,数据重新加载会中断用户体验。
一开始我们直接依赖 SavedStateHandle 存储基本类型数据,但遇到复杂对象时就束手无策。
我们的改进方式
我们最终采用以下策略来提升体验:
- 在 ViewModel 中用
SavedStateHandle管理基础状态(如搜索关键词、当前选择的 Tab 页等) - 对于复杂对象,先序列化为 Parcelable 并存储,再由 ViewModel 恢复时解析
- 结合 Jetpack 的
Navigation Component和NavController状态保存机制,避免 Fragment 栈重复初始化
我们还在基类中封装了一个通用的恢复方法:
abstract class BaseViewModel(handle: SavedStateHandle): ViewModel() {
protected val savedState = handle
}
这样不仅减少了重复代码,也避免了很多因为配置变化导致的崩溃。
成果与收益:不只是代码整洁了
重构完成后,我们收获了几个显著的变化:
- 代码结构更清晰,页面逻辑集中在 ViewModel,业务逻辑集中在 UseCase
- 单元测试覆盖率提升,ViewModel 和 UseCase 都可以脱离 UI 单独测试
- 调试效率提高,UI 不再承担过多逻辑,更容易定位问题点
- 页面切换流畅许多,得益于良好的状态管理,用户几乎感觉不到闪白或卡顿
更重要的是,团队协作变得更加顺畅。大家有了统一的架构规范,新人上手更快,交接成本降低。
经验总结 & 建议
回顾整个过程,我想给正在使用或准备使用 MVVM 的同学几点建议:
✅ 架构不是万能药,适合自己的才是最好的
MVVM 的确带来了不少好处,但也增加了学习门槛。如果你的项目非常小,或者团队还没有形成良好编码习惯,贸然使用可能会适得其反。
✅ 把控好 ViewModel 的职责边界
别让 ViewModel 变成第二个 Activity。它应该只负责协调数据、暴露状态和触发命令,而不应参与具体业务计算。
✅ 多用协程 + Flow 简化异步处理
Jetpack 的 ViewModel + Kotlin 协程 + Flow 是目前 Android 开发中比较理想的状态管理和异步编程组合。相比以前的 RxJava 或传统回调方式,可读性和稳定性都更好。
✅ 关注性能与内存
即使用了 MVVM,也不能忽视性能问题。记得检查 ViewModel 的生命周期,避免内存泄漏;尽量减少不必要的 LiveData 订阅;大数据传输建议使用 Parcelable 或 Room 持久化。
✅ 同步适配 iOS?试试 SwiftUI + Combine 或 UIKit + MVVM
如果你是跨平台团队,也可以考虑将 MVVM 的理念移植到 iOS 上。虽然原生没有像 AndroidViewModel 那样的支持类,但借助 Swift 的类型系统和 Combine 框架,也能实现类似的效果。
结语:技术的本质是服务于人
写到这里,我想起一个有意思的小插曲。那次重构期间,有一天 QA 问我:“你们最近是不是没加什么新功能?”我说:“其实我们一直在改代码结构。”她惊讶地说:“哦,怪不得我测起来越来越顺手了。”
那一刻我突然意识到:好的架构,不应该只是开发者看得见的好处,更应该让用户感受到稳定和流畅。
如果你正在为项目架构发愁,不妨勇敢迈出那一步。哪怕只是小范围试点,也能看到不小的变化。MVVM 不是银弹,但它确实是一条值得走的路。
希望这篇文章能给你一些灵感,也欢迎留言交流你的想法。
——By 一位在代码中寻找秩序的程序员 🧑💻

评论 0