移动应用架构设计:MVVM实战
移动应用架构设计实战:MVVM让代码更有“章法”
引言:从“野蛮生长”到结构清晰的演进
去年我接手了一个中型 Android 项目,原本由一个小团队负责开发。这个项目的用户量已经突破百万,但技术负债却非常严重——Activity 写得像上帝类,业务逻辑和 UI 混在一起,测试几乎无法进行。每次需求变更都像是在雷区踩地雷,一不小心就炸了整块功能模块。
我当时意识到,如果不做一次架构上的调整,后续维护成本会越来越高,甚至可能导致产品迭代停滞。于是我们决定引入 MVVM(Model-View-ViewModel) 架构来重构整个核心模块,并在这个过程中收获了许多宝贵的经验。
今天我想结合自己的亲身经历,聊一聊 MVVM 到底怎么用、有哪些坑,以及它在真实项目中的价值。
项目背景与问题现状
我们这个 App 是一个面向中小商户的进销存工具,主要提供商品管理、库存统计、销售记录、账单生成等功能。早期为了快速上线,开发节奏非常快,很多模块都是直接在 Activity/Fragment 里写网络请求、数据处理、UI 控件操作等逻辑,导致以下几个典型问题:
- 代码臃肿不可读:一个 Activity 动辄上千行代码,函数职责不清晰,阅读起来像看天书。
- 难以复用和测试:大部分逻辑嵌在界面组件中,根本没法做单元测试。
- 耦合严重:UI 层和业务层高度耦合,牵一发动全身,改一个小地方要冒很大风险。
- 协作困难:新来的同事上手慢,改动容易出错。
这些问题在版本不断迭代后变得越来越突出,尤其是在对接新的后台接口和增加自动化测试时,效率大幅下降。
为什么选择 MVVM?
当时我们团队也在讨论是否要使用更先进的架构方式,比如 MVI 或者 Clean Architecture,但我们最终选择了 MVVM + Jetpack 组件库,原因有几个:
- Jetpack ViewModel 和 LiveData 提供了良好的基础支持
- 团队成员对 MVVM 已有一定程度的了解
- 考虑到项目体量和迁移成本,MVVM 是相对稳妥的选择
- 可以平滑过渡现有代码,逐步重构
具体挑战与解决方案
挑战一:旧代码改造困难重重
一开始我们想一股脑把所有页面都重构成 MVVM,结果发现完全行不通。很多页面依赖复杂的生命周期回调和 Fragment 的交互逻辑,强行拆解反而更乱。于是我们换了个思路:
先小范围试点,在新功能中实践,再逐步回溯老代码
我们选了一个比较独立的功能模块:库存预警通知页,作为第一个改造对象。它的功能包括:
- 从服务器拉取库存预警列表
- 根据不同状态筛选和排序
- 点击某条记录进入详情
- 页面数据变化实时刷新
这部分页面结构简单,逻辑清晰,适合练手。
解决方案一:合理分层,分离关注点
我们按照 MVVM 分成了三层:
- View(Activity / Fragment):只负责 UI 渲染和事件监听
- ViewModel:持有 LiveData 数据,响应 View 事件并调用 Model
- Repository(Model):统一管理数据来源,包括本地数据库和远程 API
举个例子,点击某个按钮触发数据刷新:
class StockWarningViewModel : ViewModel() {
private val repository = StockRepository()
val stockList = MutableLiveData<List<StockItem>>()
fun refreshData() {
viewModelScope.launch {
val result = repository.fetchStockWarnings()
stockList.postValue(result)
}
}
}
这样 View 只需要观察 stockList 的变化即可:
viewModel.stockList.observe(this, Observer { list ->
adapter.submitList(list)
})
这样做的好处是,View 不知道数据是怎么来的,ViewModel 不关心 UI 怎么显示,各司其职。
挑战二:生命周期与数据绑定问题
由于 Android 的生命周期机制复杂,经常出现诸如:
- 页面销毁后回调未取消,导致崩溃
- LiveData 多次重复更新 UI,浪费资源
为了解决这些问题,我们做了以下几点优化:
- 统一使用 LifecycleScope 或 ViewModelScope 启动协程
- LiveData 尽量使用 Transformations.map/filter 做转换
- 对于不需要粘滞事件的场景,采用 SingleLiveEvent 或 Event Wrapper 设计模式
- 避免将 Context 长期持有
例如我们封装了一个 Event<T> 类,专门用于解决仅触发一次的通知需求:
class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
}
这样即使页面重建了,也不会重复弹出 Toast 或导航。
挑战三:数据持久化和缓存策略
随着业务扩展,我们需要考虑如何利用本地存储减少网络请求,同时提高用户体验。我们采用了 Room + Retrofit 的组合方式,实现本地与远程数据联动:
- Repository 优先查询本地数据库
- 若本地无数据或过期,则请求网络并插入数据库
- 使用 Rx 或 Coroutine 做异步数据处理
Room 的注解方式非常好用,基本可以做到一行 SQL 都不用写,而且编译期就会帮你检查语句合法性。
挑战四:跨平台与 Flutter 的对比思考
虽然这次是纯 Android 项目,但在公司内部还有 Flutter 的一些探索项目。我们在回顾技术选型时也做了些比较:
| 特性 | MVVM + Jetpack | Flutter |
|---|---|---|
| 学习曲线 | 中等偏下,Android 开发友好 | 较陡,需掌握 Dart |
| UI 一致性 | 受制于原生样式 | 自带渲染引擎,一致性高 |
| 数据流管理 | ViewModel + LiveData + Repository | BLoC/Riverpod/Cubit |
| 代码复用 | 有限(Java/Kotlin) | 高(iOS/Android/H5通吃) |
总的来说,如果你是一个 Android 原生项目,MVVM 仍然是目前最成熟、最实用的选择之一。而 Flutter 更适合希望多端统一、降低开发人力的团队。
成果展示与实际收益
重构完成后的半年内,我们明显感受到了几个关键改进:
- 可维护性大幅提升:模块之间界限清晰,修改 bug 更加精准,新人上手更快
- 测试覆盖率提高:ViewModel 几乎不含 Android Framework 依赖,可以用纯 Kotlin 单元测试
- 发布流程顺畅:因结构更清晰,提交审核被拒的情况变少了(特别是 crash 相关)
- 性能优化更容易:比如通过 LiveData 观察者机制减少了不必要的 UI 更新

有一次我们遇到一个严重的内存泄漏问题,就是因为某个 ViewModel 中引用了 Context 并未释放。借助 LeakCanary 很快定位并修复了问题,这种可见性和可控性在老式 MVC 结构中几乎是做不到的。
实用经验分享
根据这几年的移动开发经验,我总结了几条关于使用 MVVM 的建议,希望能给正在做类似尝试的朋友一点启发:
✅ 1. 架构不是银弹,适合自己最重要
MVVM 不是万能钥匙,也不是唯一答案。你要根据团队的技术栈、项目大小、未来扩展方向来选择最适合你的架构方式。
✅ 2. 保持 ViewModel 的“干净”,避免污染
不要在里面持有 Context,也不要把业务逻辑塞得太满。你可以通过注入的方式传入 Repository,或者通过 useCase 把逻辑进一步拆解出去。
✅ 3. LiveData 还是 Flow?这取决于你的团队熟悉程度
Flow 支持冷热流、背压控制、转换操作符更灵活,但也需要更高的学习成本。LiveData 在生命周期安全方面做得很好,适合大多数中大型 Android 团队。
✅ 4. 不要忽视 UI 层的轻量化
即便用了 MVVM,也不要让 Fragment/Activity 变成“伪空壳”。合理的 UI 状态管理、错误提示逻辑还是要放在 View 层来处理。建议使用 State 模式组织 UI 状态。
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
这样可以在 View 中统一处理各种状态切换。
✅ 5. 日志打印 & 崩溃上报不能少
MVVM 架构让你的代码更优雅,但如果缺乏完善的日志系统,你仍然会在生产环境束手无策。一定要接入 Crashlytics 或 Sentry 之类的服务,及时发现问题。
结尾感悟:好的架构,是为了更好的未来
重构初期,确实遇到了不少阻力。有人说:“现在跑得好好的,为啥还要折腾?”也有人说:“写了这么多层,是不是有点过度设计?”
但我始终相信一句话:代码是写给人看的,偶尔给机器跑一下。
一个好的架构不是为了炫技,而是为了让团队走得更远,让产品能持续进化。MVVM 虽然不是最新最酷的技术,但它足够稳定,已经被无数项目验证过,并且拥有强大的生态支持。
如果你正面对一个日益庞大的 Android 项目,不妨也试试用 MVVM 来理清代码的脉络。哪怕只是在一个小功能中开始,也能为你打开通往更好架构的大门。
最后送大家一句我喜欢的话:
“编程就像写作,清晰的结构比华丽的技巧更重要。”
祝各位编码愉快,架构顺手!

评论 0