移动应用架构设计:MVVM实战
开篇:为什么是 MVVM?

作为一名在互联网公司工作的移动开发工程师,我这些年几乎经历过所有的主流 Android 架构模式。从最开始的 MVC,到 MVP,再到现在的 MVVM,每一步演进背后都是一段与代码复杂性、团队协作和可维护性作斗争的过程。
今天想聊聊我在一个真实项目中使用 MVVM(Model-View-ViewModel) 的实战经验,包括为什么要选择它、遇到的问题、怎么解决的,以及最终带来的收益。希望这篇笔记能给正在为架构选型头疼的同学一点参考。
问题描述:老项目陷入“逻辑泥潭”

去年我们接手了一个中等规模的老项目,主要功能是一个内容社区 App,用户可以浏览图文内容、发布动态、评论互动等。虽然整体功能已经上线运营了一段时间,但技术债堆积严重。
具体表现有几个方面:
- Activity/Fragment 职责混乱:很多页面的业务逻辑直接写在 Activity 中,动辄上万行代码。
- 界面与数据耦合严重:UI 层直接依赖 Repository 和网络请求结果,调试和测试非常困难。
- 团队协作成本高:多人修改同一个文件容易冲突,Review 也难以下手。
- 单元测试基本没有覆盖:所有业务都在 UI 层,Mock 数据困难,导致质量保障薄弱。
面对这些问题,我们意识到必须对整个项目的架构进行一次升级重构,不能再继续“缝缝补补又一年”。
解决方案:引入 MVVM,解耦视图与逻辑
为什么是 MVVM?
MVVM 的核心在于 职责分离 和 数据驱动视图。通过 ViewModel 管理 UI 相关的数据状态,结合 LiveData 或 RxJava 实现观察者模式,我们可以做到:
- View 只负责展示
- ViewModel 管理 UI 相关的状态变化
- Model 处理数据获取和持久化
这种结构特别适合我们在团队开发中明确分工,并且支持后续的自动化测试和组件复用。
我们的架构设计
在实际落地过程中,我们做了一些定制化的适配:
App Module
├── ui
│ ├── feed
│ │ ├── FeedFragment.kt
│ │ ├── FeedAdapter.kt
│ ├── detail
│ ├── DetailActivity.kt
├── viewmodel
│ ├── FeedViewModel.kt
│ ├── DetailViewModel.kt
├── repository
│ ├── ContentRepository.kt
├── model
│ ├── ContentItem.kt
├── network
│ ├── ApiService.kt
└── util
└── ...
在这个基础上,我们还使用了如下几个关键组件和技术栈:
- AndroidViewModel + SavedStateHandle:用于处理生命周期感知的数据保存与恢复。
- LiveData + Transformations:用来暴露 UI 需要的状态数据。
- Repository 模式:统一管理本地数据源(Room)和远程数据源(Retrofit)。
- Koin 或 Dagger(视项目而定):注入 ViewModel 与 Repository。
- 协程 + Flow:异步任务与响应式数据流的首选方式。
代码实践:以内容首页为例
这里我们以 App 首页 Feed 流模块为例,来讲解一下 MVVM 在实际项目中的结构和实现方式。
1. Model 层定义数据实体
data class FeedContent(
val id: String,
val title: String,
val author: String,
val imageUrl: String,
val likes: Int,
val comments: Int
)
2. Repository 获取数据
class FeedRepository @Inject constructor(
private val remoteDataSource: FeedRemoteDataSource,
private val localDataSource: FeedLocalDataSource
) {
suspend fun fetchLatestFeed(): List<FeedContent> {
val data = remoteDataSource.fetchFeedFromNetwork()
// 可以缓存到本地数据库或 SharedPreferences
return data
}
}
3. ViewModel 处理业务逻辑

class FeedViewModel @ViewModelInject constructor(
private val repository: FeedRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _feedData = MutableLiveData<List<FeedContent>>()
val feedData: LiveData<List<FeedContent>> = _feedData
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
fun loadFeed() {
viewModelScope.launch {
_loading.value = true
try {
val result = repository.fetchLatestFeed()
_feedData.value = result
} catch (e: Exception) {
_error.value = "加载失败"
} finally {
_loading.value = false
}
}
}
}
4. View 层绑定 ViewModel
class FeedFragment : Fragment() {
private val viewModel: FeedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = FeedAdapter()
binding.feedRecycler.adapter = adapter
viewModel.feedData.observe(viewLifecycleOwner, { items ->
adapter.submitList(items)
})
viewModel.loading.observe(viewLifecycleOwner, { isLoading ->
binding.progressBar.isVisible = isLoading
})
viewModel.error.observe(viewLifecycleOwner, { errorMsg ->
Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_SHORT).show()
})
if (savedInstanceState == null) {
viewModel.loadFeed()
}
}
}
这样我们就完成了从数据获取、处理到 UI 更新的一整套流程,逻辑清晰,结构分明。
踩坑经验:实战中遇到的真实问题
任何架构都不是银弹,在实际推进 MVVM 的过程中我们也踩了不少坑。
坑点一:ViewModel 生命周期理解不透彻
我们曾经在一个页面里在 onCreate 中初始化数据,但在旋转屏幕时重复调用加载接口,造成多次网络请求。
解决方案:充分利用 viewModelScope 和 SavedStateHandle 来保留中间状态,避免重复加载。
坑点二:LiveData 的更新方式不当引发性能问题
早期有人习惯在 ViewModel 中使用 MutableLiveData 暴露出来让 View 修改状态,这实际上违反了数据不可变原则,还容易导致内存泄漏。
正确姿势:只暴露 LiveData,并通过封装方法间接改变内部状态。
坑点三:多个 Fragment 共享 ViewModel 出现数据污染
我们在两个相关页面共享一个 ViewModel 后,出现了互相干扰的问题。
解决办法:合理利用 by viewModels() 和 by activityViewModels() 控制作用域范围,或者拆分成子 ViewModel。
效果总结:重构之后的变化
经过三个月的持续重构与新功能开发并行推进,我们的 App 在以下几个维度发生了明显变化:
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 页面层级结构 | 混乱,单个类动辄几千行 | 层级清晰,每个类职责单一 |
| 团队协作效率 | 多人开发频繁冲突 | 明确边界后协作顺畅 |
| 单元测试覆盖率 | 不足 10% | 提升至 65%+ |
| Bug 定位速度 | 通常需要走完整路径才能发现 | 可快速定位逻辑错误 |
| 新人入门难度 | 文档缺失、代码难读 | 结构统一,文档易写 |
尤其让人欣慰的是,原本需要两周上线的新功能,现在一周内就能稳定交付,迭代节奏明显加快。
经验分享:我的几点建议

1. 架构不是越先进越好,合适才是王道
MVVM 是目前 Android 官方推荐架构之一,但在一些小型项目或者实验性质的项目中,不一定非要强推。有时候 MVP 更轻量,甚至简单的分层也能满足需求。
2. ViewModel 并不是万能的“救命稻草”
不要把 ViewModel 当成第二个 Activity,里面照样不能放太多业务细节。该拆的业务逻辑还是得抽出去,比如放到 UseCase 或 Interactor 层。
3. 注意不同平台的适配差异
我们在一个跨平台项目中同时使用 Jetpack Compose 和 Flutter,发现部分 ViewModel 状态管理和生命周期存在不一致问题。因此要特别注意跨平台场景下的状态同步策略。
4. 关注用户体验与性能优化
在使用 LiveData 和 Flow 的时候,如果频繁触发刷新,很容易引起 UI 抖动。这时候可以通过 debounce、distinctUntilChanged、防抖合并等方式优化。
例如:
viewModel.feedData
.debounce(300)
.distinctUntilChanged()
.observe(...)
还能有效减少重复渲染。
5. 别忘了发版过程中的“隐形成本”
MVVM 结构良好也方便我们在 App 发布阶段更好地配合灰度测试和埋点分析。由于数据层与 UI 层彻底分离,可以在 Repository 层加一层“日志装饰器”,记录每一次数据变更和错误来源,极大提升了排查效率。
尾声:写给自己和未来
MVVM 不是终点,也不应该是我们追求的唯一目标。它只是帮助我们写出更高质量代码的一种工具和思维方式。
回过头来看这段重构之路,其实收获最大的并不是技术上的提升,而是团队对于“代码整洁”这件事达成了共识。大家不再纠结于某个函数到底写在哪,更多讨论的是如何将逻辑抽象出来,如何让下一位接手的人更容易理解。
也许这才是真正的工程思维。
如果你也在考虑使用 MVVM 或者已经在使用了,欢迎留言交流。愿我们在技术的路上少些焦虑,多些笃定。

评论 0