从实战中理解 MVVM:我在移动应用架构设计中的那些事儿
作为一个在移动开发一线“摸爬滚打”了多年的老兵,我经历了 MVC、MVP 到如今流行的 MVVM 架构。每一次架构的演变,都是为了解决前一阶段的痛点和复杂性。今天想通过一个实际项目,聊聊我是如何在 Android 上实践 MVVM(Model-View-ViewModel) 架构的,并且在这个过程中踩过的坑以及收获的经验。
背景介绍:一个看似普通的项目

事情发生在去年上半年,我们团队接手了一个医疗健康类 App 的重构任务。这个 App 最初是用 MVP 模式构建的,随着功能越来越多,Activity 和 Presenter 层的代码量越来越大,耦合也越来越严重。特别是在处理一些复杂的 UI 交互和数据绑定时,每次修改都需要牵一发而动全身。
项目的用户群体主要是中老年人,对操作流程要求简单直观,对页面流畅性和加载速度也有较高的期待。这意味着除了功能稳定外,性能优化、代码可维护性、团队协作等方面也变得尤为重要。
正是在这种背景下,我们决定尝试使用 Google 推荐的 MVVM 架构 + Jetpack 组件 来进行重构。
遇到的问题:复杂的数据状态管理

在原有 MVP 架构下,Presenter 承担了大量的业务逻辑和状态处理职责。但问题在于,Presenter 很难脱离 Context 存在,这就导致其生命周期管理和测试难度非常高。更麻烦的是,当多个 Fragment 或 Activity 需要共享某个数据源时,很容易出现数据不一致或更新延迟的问题。
举个例子:比如我们有一个“测量数据”的列表页和一个图表展示页,两个页面都依赖同一个传感器数据。每次数据有新条目添加进来,不仅要通知其中一个页面刷新,还要保证另一个页面的状态同步更新。当时我们用了各种回调和监听器,结果代码冗长、逻辑混乱,调试极其困难。
此外,还有几个关键痛点:
- UI 绑定繁琐:大量的
findViewById和手动绑定 - 生命周期管理混乱:经常因为忘记取消订阅或释放资源导致内存泄漏
- 测试困难:Presenter 不易做单元测试,缺乏清晰的数据流追踪机制
解决方案:转向 MVVM + Jetpack 架构

结合官方推荐的最佳实践,我们采用了如下技术组合来实现 MVVM 架构:
- ViewModel:作为与 View 通信的桥梁,处理 UI 相关数据逻辑,具备生命周期感知能力。
- LiveData / StateFlow:观察数据变化,自动刷新 UI。
- Repository:统一数据源访问入口,屏蔽数据来源差异(本地/网络)。
- Room / Retrofit:本地数据库和网络请求库。
- DataBinding / Compose(部分试点):减少模板代码,提高 UI 与 ViewModel 的绑定效率。
整个架构层级大致如下:
+-------------------+
| View |
| (Activity/Frag) |
+--------+----------+
|
v
+--------+----------+
| ViewModel Layer |
| - ViewModel |
| - LiveData |
+--------+----------+
|
v
+--------+----------+
| Repository |
| - 数据提供层 |
| - 网络 + 本地 |
+--------+----------+
|
v
+--------+----------+
| Data Source |
| - Room DB |
| - API Service |
+-------------------+
这样做的好处非常直接:
- ViewModel 帮助 View 保持轻量,避免数据泄露;
- LiveData 实现了响应式更新机制;
- Repository 解耦了上层逻辑与数据获取方式;
- 整体结构清晰,便于多人协作和后期扩展。
代码实践:以“测量数据模块”为例
这里分享一段真实业务模块的代码结构。
ViewModel 示例
class MeasureViewModel : ViewModel() {
private val repository = MeasureRepository()
// 使用 liveData 封装异步数据请求
val measureList: LiveData<List<MeasureItem>> = liveData {
val data = repository.loadMeasurements()
emit(data)
}
fun refreshData() {
viewModelScope.launch {
repository.refreshDataFromNetwork()
}
}
}
Repository 示例
object MeasureRepository {
private val apiService = ApiService.create()
private val db by lazy { AppDatabase.getInstance().measureDao() }
suspend fun loadMeasurements(): List<MeasureItem> {
val localData = db.getAll()
if (localData.isEmpty()) {
val networkData = apiService.fetchMeasurements()
db.insertAll(networkData)
}
return db.getAll()
}
suspend fun refreshDataFromNetwork() {
val newData = apiService.fetchMeasurements()
db.clearAndInsert(newData)
}
}
在 Fragment 中使用
class MeasureListFragment : Fragment() {
private val viewModel: MeasureViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.measureList.observe(viewLifecycleOwner) { list ->
adapter.submitList(list)
}
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.refreshData()
}
}
}
这样的组织方式不仅逻辑清晰,而且非常容易做自动化测试 —— 因为 ViewModel 完全不持有任何视图引用,可以在 JVM 单元测试中独立运行。
踩坑经验:写代码是一回事,落地是另一回事
虽然理论听起来很美好,但在实际实施中我们也遇到了不少坑。
1. LiveData 和 LifecycleOwner 的作用域问题
初期我们在 Activity 中使用 lifecycleOwner 作为观察者上下文没问题,但在 Fragment 中如果不小心用了错误的作用域,就可能导致观察不到事件或者提前解绑。
解决方法: 在 Fragment 中务必使用 viewLifecycleOwner 而不是 this 作为 LifeCycleOwner。
2. 数据重复订阅导致多次刷新
某些场景下,ViewModel 中可能封装了多个网络请求或数据操作,在没有合理缓存机制的情况下,会导致频繁调用接口,浪费资源。
解决办法: 引入 DistinctLiveData,或者使用 StateFlow 替代部分 LiveData,利用其可以控制发射频率的能力。
val state: StateFlow<LoadState> = _state
3. ViewModel 泄漏问题(尤其是带参数的)
ViewModel 如果持有了 Context 或者非静态内部类,就可能造成内存泄漏。特别是当我们使用 SavedStateHandle 传递参数时,要注意不要引入强引用。
建议做法: ViewModel 只接受基本类型或 Parcelable 类型的参数;Context 相关的操作交给 UseCase 或 Repository 去完成。
4. 旧代码改造兼容问题
很多地方原来使用的是 MVP 或 MVC 架构下的 Callback 回调方式,现在改为观察模式之后,数据流的变化需要重新梳理业务逻辑。
处理策略: 逐步替换,确保每一层都有对应的单元测试覆盖,防止改出 bug。
效果总结:架构升级带来实实在在的好处

经过几个月的重构和迭代,我们终于把主要模块都迁移到了 MVVM 架构。效果还是非常明显的:
开发效率提升
- 大大减少了手动数据更新和状态管理的代码量;
- 更方便地复用 ViewModel 和 Repository;
- 团队成员更容易理解不同模块之间的关系。
Bug 数量明显下降
由于各层职责明确,加上良好的观测机制,过去常见的因数据不同步、状态未正确刷新等问题大大减少。
性能有所优化
借助 ViewModel 的生命周期感知能力和 LiveData 的懒加载特性,不必要的计算被有效规避,App 运行更加流畅。
测试覆盖率提高
ViewModel 层几乎可以全部用 JVM 单元测试完成逻辑验证,不再依赖 Instrumentation 测试,极大提升了测试效率和质量。
经验分享:几点给同行朋友的建议
如果你也打算在自己的项目中实践 MVVM 架构,下面是我总结的一些实用建议,希望能帮你少走弯路:
✅ 1. 从小模块开始尝试,逐步推进
不要一口气重构成熟的 MVVM 架构,先从一个核心功能模块做起,熟悉后再逐步推广。
✅ 2. 比较 LiveData、StateFlow、RxJava 等方案的适用场景
不同的项目有不同的需求,选择适合团队的技术栈非常重要。比如对于中小型 App,LiveData 已经足够;而对于更复杂的响应式场景,可以考虑 Combine 或 Flow。
✅ 3. 注意状态管理的集中化
如果多个 ViewModel 需要共享状态,建议提取出一个 SharedViewModel 或通过 Repository 管理全局状态,避免各处自行管理造成数据混乱。
✅ 4. 结合 Clean Architecture 分层思想
MVVM 本身只是表示层的一种模式,真正要把架构做得干净,还需要配合 UseCase(Interactor)等中间层,进一步解耦业务逻辑。
✅ 5. 别忽视编译时安全机制
使用 Kotlin 的 Null Safety、sealed class、enum class 等特性,有助于在编译期就发现潜在问题,尤其适用于状态管理类的场景(如 Loading, Success, Error)。
✅ 6. 移动平台适配不可忽视
在多设备适配方面,注意不同屏幕尺寸的布局表现,合理使用 ConstraintLayout 或 Jetpack Compose 的响应式布局系统。另外,针对低端机型做好性能兜底处理。
最后的一点感想
其实,不管是哪一种架构,都没有绝对的优劣,只有适不适合当前的项目和团队。MVVM 并不是银弹,但它确实让我们更好地分离关注点、提升代码质量和可维护性。
在我个人看来,一个好的架构应该是让开发者轻松写代码、快速定位问题、容易做测试和持续集成 —— MVVM 在这些方面给了我很大的信心。
如果你正在犹豫要不要学习或者切换 MVVM,我的建议是——“早点开始总比一直拖延好”。越早建立良好的架构意识,未来的维护成本就会越低,团队的合作效率也会越高。
愿你也能在架构演进的路上走得更稳、更远。
By:一个热爱写代码的 Android 工程师 🤓

评论 0