移动应用架构设计:MVVM实战,我的一次真实项目经验分享

邓秀兰
2025-06-27 03:05
阅读 750

引言:为什么我选择了 MVVM

引言:为什么我选择了 MVVM

2023年初,我接手了一个中型的 Android 应用重构项目。这个项目已经运行了两年多,早期采用了 MVP 架构,但随着功能模块越来越多,代码耦合严重、测试困难、维护成本高成了老大难问题。

当时我们团队内部对是否重构展开过激烈讨论。有人主张继续在原有架构上优化,也有人建议直接引入 Jetpack 组件进行重构。最终我们决定尝试使用 MVVM(Model-View-ViewModel) 架构,并结合 Jetpack 的 ViewModel 和 LiveData 来推进重构工作。

在这篇文章里,我想通过这次经历,聊聊我们在移动应用中如何落地 MVVM 架构,特别是在实际开发过程中遇到的一些问题和解决方案。如果你也在考虑或者正在实践 MVVM,相信这些内容能带来一些启发。


项目背景:从 MVP 走向 MVVM

项目背景:从 MVP 走向 MVVM

我们的 App 主要面向金融行业用户,提供数据查询、图表分析、实时推送等核心功能。随着产品迭代加速,业务逻辑复杂度陡增,MVP 架构下的 Presenter 层逐渐臃肿,且大量业务逻辑分散在 Activity 和 Fragment 中,导致:

  • 页面间通信混乱
  • 数据状态管理困难
  • 单元测试难以覆盖
  • 内存泄漏频发

这些问题严重影响了新功能的上线速度和产品的稳定性。面对这种情况,我们决定进行架构升级,目标是:

  1. 提升代码可维护性
  2. 实现 UI 与数据解耦
  3. 支持快速迭代与高质量交付
  4. 增强组件复用能力

挑战一:业务逻辑复杂,数据流混乱

挑战一:业务逻辑复杂,数据流混乱

重构的第一步,是从 MVP 切换到 MVVM。最开始我们就遇到了一个现实问题:原有的数据处理逻辑混杂在多个类中,不知道该把哪些逻辑放到 ViewModel,哪些留在 Repository

举个例子,在一个“持仓数据详情页”中,页面需要根据不同的筛选条件展示不同的数据统计结果,同时还需要拉取服务器数据、本地缓存、计算百分比变化等多个操作。

最初的设计是把这些都扔进 ViewModel,导致 ViewModel 类变得庞大臃肿。我们很快意识到这是不可持续的做法。

我们的应对策略

我们参考了官方推荐的架构分层模型,并做了一些本地化调整:

UI -> ViewModel <-> LiveData/StateFlow  
          ↓  
      Repository  
          ↓  
Local Data Source / Remote Data Source

具体来说,我们做了几件事:

  • 将所有数据获取封装到 Repository 层,保持单职责原则
  • ViewModel 只负责协调请求和更新 UI 需要的数据结构(如转换 DTO → ViewData)
  • 使用 LiveDataStateFlow 管理状态变更并通知 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)


![原生应用架构-1](https://code-guide.oss.shanghai.autogptai.club/common/file/download?name=date2025062703/311913be-1e68-4d7d-8752-8f62c3192ac0.jpg)


inputValid.launchAndCollectIn(lifecycleScope) {
    submitButton.isEnabled = it
}

这种方式不仅逻辑清晰,还能自动处理多个输入间的依赖关系。从此告别手动注册一堆 Observer 的痛苦。


挑战三:适配不同屏幕尺寸,状态恢复成难题

由于我们的 App 需要兼容平板、手机甚至折叠屏设备,页面状态恢复一直是个难点。特别是当用户切换屏幕方向时,数据重新加载会中断用户体验。

一开始我们直接依赖 SavedStateHandle 存储基本类型数据,但遇到复杂对象时就束手无策。

我们的改进方式

我们最终采用以下策略来提升体验:

  1. 在 ViewModel 中用 SavedStateHandle 管理基础状态(如搜索关键词、当前选择的 Tab 页等)
  2. 对于复杂对象,先序列化为 Parcelable 并存储,再由 ViewModel 恢复时解析
  3. 结合 Jetpack 的 Navigation ComponentNavController 状态保存机制,避免 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝