移动应用架构设计:MVVM实战
引言:为什么我会选择深入 MVVM 架构?

作为一名在互联网公司工作的移动端开发工程师,我经历了从 MVP 到 MVVM 架构的演变过程。记得刚接手第一个项目时,还是用传统的 MVC 模式写代码,随着业务越来越复杂,Activity/ViewController 的代码量也疯狂增长,测试难、维护难、耦合高成了家常便饭。
后来我们尝试引入了 MVVM(Model-View-ViewModel)架构模式,一开始只是抱着试试看的心态,没想到它不仅显著改善了代码结构,还提升了团队协作效率和开发体验。更重要的是,它让我们的应用更具可测试性和扩展性——这些正是现代移动开发中非常关键的素质。
在这篇文章中,我会结合我在一个实际项目中的经验,详细分享我是如何落地 MVVM 架构的,过程中踩过的坑、收获的心得,以及最终取得的效果。希望对大家有所启发。
项目背景与挑战

去年我参与了一个内部孵化的社交类 App 开发项目,定位是一款轻量化、实时更新内容流的互动社区产品。初期版本需要支持用户登录、浏览内容卡片流、评论互动、消息推送等基本功能。技术选型方面,Android 使用 Kotlin + Jetpack 组件,iOS 使用 Swift + Combine(后续也有迁移到 SwiftUI 的尝试)。
当时最大的痛点是 页面逻辑复杂、数据流动不清晰。举个例子,比如内容详情页:
- 数据来自多个接口(内容主体信息、用户信息、评论列表、是否点赞、关注状态)
- 各种 UI 状态切换频繁(加载中、空数据、出错重试、正常显示)
- 数据联动频繁(点赞后更新 UI 状态、新增评论后刷新列表)
最初我们没有明确的架构约束,各个页面的 ViewModel 都放在 Activity 中处理业务逻辑,导致页面臃肿,甚至出现重复代码的情况。
于是我们决定尝试采用 MVVM 架构重新设计页面结构,并结合 Android 的 ViewModel、LiveData 和 iOS 的 ObservableObject、Combine 来统一管理状态和数据流。
我们的解决方案:MVVM 架构实践

核心组件划分
我们以 Android 为例,构建 MVVM 的核心层如下:
View 层(UI 层)
- 对应 Activity / Fragment / Compose UI
- 只负责渲染 UI 和响应用户事件,不做任何数据操作
ViewModel 层
- 负责持有 UI 相关的数据和状态
- 将数据暴露为 LiveData 或 StateFlow,供 View 观察更新
- 不持有任何 Context,不直接操作 View
Repository 层
- 封装数据源获取逻辑(本地缓存 + 网络请求)
- 提供统一的数据访问接口给 ViewModel 调用
UseCase 层(可选)
- 复杂业务逻辑抽象成独立 UseCase 类,由 ViewModel 调用
- 适用于多页面复用逻辑场景,如“发布内容”、“删除评论”
iOS 方面,使用类似的分层方式,结合
@StateObject、@ObservedObject、NotificationCenter、Combine来实现双向绑定和异步通信。
示例:重构内容详情页
1. Repository 数据聚合
class ContentDetailRepository {
suspend fun fetchContentDetail(contentId: String): Result<Content> {
val content = apiService.getContentById(contentId)
val comments = apiService.getCommentsByContentId(contentId)
return Result.Success(
ContentDetailData(content, comments)
)
}
}
2. UseCase 抽象业务逻辑
class LikeContentUseCase(private val repository: ContentDetailRepository) {
suspend operator fun invoke(contentId: String): Result<Unit> {
return repository.likeContent(contentId)
}
}
3. ViewModel 管理状态
class ContentDetailViewModel : ViewModel() {
private val _contentState = MutableLiveData<Content>()
val contentState: LiveData<Content> get() = _contentState
private val _commentList = MutableLiveData<List<Comment>>()
val commentList: LiveData<List<Comment>> get() = _commentList
fun loadContentDetail(contentId: String) {
viewModelScope.launch {
when (val result = repository.fetchContentDetail(contentId)) {
is Result.Success -> {
_contentState.postValue(result.data.content)
_commentList.postValue(result.data.comments)
}
else -> {
// handle error
}
}
}
}
fun toggleLike(contentId: String) {
viewModelScope.launch {
likeUseCase.invoke(contentId)
updateLikeStatus(contentId) // 本地更新UI
}
}
}
4. View 层观察 ViewModel 并更新 UI
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProvider(this).get(ContentDetailViewModel::class.java)
viewModel.contentState.observe(this, { content ->
bindContentToView(content)
})
viewModel.commentList.observe(this, { list ->
commentAdapter.submitList(list)
})
}
这样拆分之后,UI 层只负责观察 ViewModel 提供的状态变化,而所有数据获取、状态变更都通过 ViewModel 控制,实现了很好的解耦。
踩坑经验总结
坑一:ViewModel 生命周期理解不清,导致内存泄漏
刚开始使用 ViewModel 时,有个误区是以为只要用了 ViewModel 就不会内存泄漏了。但我们在一个页面中不小心把网络请求回调持有了整个 ViewModel,结果发现页面关闭后 ViewModel 仍被 retain,最终查到是因为在协程中引用了上下文或全局单例对象未释放。
教训:不要在 ViewModel 中持有任何与生命周期相关的引用(Context、Application、Activity)。对于网络请求,在 ViewModel 作用域中发起,使用
viewModelScope自动取消即可。
坑二:UI 状态同步混乱
早期我们尝试直接让 View 监听 Repository 层返回的数据结果,导致多个页面间数据状态不同步、加载动画重复触发等问题。
改进方案:将状态统一交给 ViewModel 管理,View 只订阅 ViewModel 提供的状态,避免各自监听底层资源。
坑三:iOS 上 Combine 的线程问题
在 iOS 侧我们最初使用了 Combine 进行数据流处理,但在并发操作时经常遇到 UI 更新异常的问题,例如:
- 接收到异步数据后更新 @Published 变量时报错
- 切换主队列失败,导致 UI 冻住
解决方法:
- 使用
.receive(on: DispatchQueue.main)显式指定主线程更新 - 使用
assign(to:)时注意变量强弱引用,避免循环持有
效果与收益
重构后,整个项目的代码质量明显提升:
- 模块职责清晰:各层边界明确,便于协作和交接
- 减少重复代码:Repository 和 UseCase 的复用率大幅提升
- 更容易测试:ViewModel 不依赖 UI,可以单独单元测试
- 提升迭代效率:新需求接入成本更低,风险更小
上线后,我们也做了一些性能监控:
| 指标 | 改进前 | 改进后 |
|---|---|---|
| 页面首次加载耗时 | 900ms+ | 750ms 左右 |
| 内存占用峰值 | 180MB | 160MB |
| ANR 发生次数(日均) | 8-10次 | 2-3次 |
可以看到,MVVM 架构不仅带来了良好的结构化优势,在性能层面也有一定正向作用。
实战心得与建议
✅ 架构不是越重越好

在实际开发中,过度设计会带来不必要的复杂度。MVVM 更适合业务相对复杂的页面,或者需要长期维护的产品。对于小型 App 或实验性项目,KISS(Keep It Simple Stupid)原则依然适用。
✅ 数据驱动优先于 UI 操作
MVVM 的精髓在于用数据驱动 UI,而不是反过来。尽量避免直接在 ViewModel 中写 Toast.show、startActivity 等操作,而是通过 LiveData/State 暴露状态,UI 根据状态做出反应。
✅ 配套工具要跟上
- 安卓端推荐使用 Hilt 做 DI
- iOS 可搭配 Swinject 或 Resolver 等库注入依赖
- 统一错误处理机制,避免每个 UseCase 单独 catch
✅ 注意跨平台一致性
如果我们同时开发双端 App,MVVM 的思想可以帮助我们保持架构一致性。虽然具体实现不同(Kotlin vs Swift),但可以制定统一的设计规范,方便两端协作和联调。
✅ 应用市场的适配经验
MVVM 在提升可测试性的同时,也能帮助我们在灰度发布、A/B 测试等运营场景下快速切换逻辑。比如我们曾将一个评论功能的入口开关封装到了 Repository 层,无需改 UI 即可动态控制。
结语:架构服务于人

回头来看,MVVM 的最大价值并不是它有多么炫技的技术点,而是让我们养成了状态管理和数据隔离的好习惯。它帮我们把“逻辑”和“展示”分离,“业务”和“界面”分层,这对提高开发效率、降低沟通成本至关重要。
架构本身只是一个工具,真正的关键是我们如何根据项目实际情况,灵活运用。就像开车一样,方向盘在你手上,路该怎么走,还得你自己判断。
如果你还在犹豫要不要尝试 MVVM,我的建议是:先从小项目练起,从一个小页面开始重构,感受一下它给你带来的便利。慢慢地你会发现,这是一条值得坚持的路。
希望这篇文章能帮你少走一些弯路,也欢迎你在评论区交流你的经验和疑问。我们共同进步!
文章作者:一位在一线互联网大厂搬砖多年的移动端开发者 📲
首发于个人博客 · 欢迎转载注明出处

评论 0