移动应用架构设计:MVVM实战

开发者晨报
2025-06-15 08:55
阅读 501

移动应用架构设计实战:MVVM让代码更有“章法”


引言:从“野蛮生长”到结构清晰的演进

去年我接手了一个中型 Android 项目,原本由一个小团队负责开发。这个项目的用户量已经突破百万,但技术负债却非常严重——Activity 写得像上帝类,业务逻辑和 UI 混在一起,测试几乎无法进行。每次需求变更都像是在雷区踩地雷,一不小心就炸了整块功能模块。

我当时意识到,如果不做一次架构上的调整,后续维护成本会越来越高,甚至可能导致产品迭代停滞。于是我们决定引入 MVVM(Model-View-ViewModel) 架构来重构整个核心模块,并在这个过程中收获了许多宝贵的经验。

今天我想结合自己的亲身经历,聊一聊 MVVM 到底怎么用、有哪些坑,以及它在真实项目中的价值。


项目背景与问题现状

我们这个 App 是一个面向中小商户的进销存工具,主要提供商品管理、库存统计、销售记录、账单生成等功能。早期为了快速上线,开发节奏非常快,很多模块都是直接在 Activity/Fragment 里写网络请求、数据处理、UI 控件操作等逻辑,导致以下几个典型问题:

  1. 代码臃肿不可读:一个 Activity 动辄上千行代码,函数职责不清晰,阅读起来像看天书。
  2. 难以复用和测试:大部分逻辑嵌在界面组件中,根本没法做单元测试。
  3. 耦合严重:UI 层和业务层高度耦合,牵一发动全身,改一个小地方要冒很大风险。
  4. 协作困难:新来的同事上手慢,改动容易出错。

这些问题在版本不断迭代后变得越来越突出,尤其是在对接新的后台接口和增加自动化测试时,效率大幅下降。


为什么选择 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,浪费资源

为了解决这些问题,我们做了以下几点优化:

  1. 统一使用 LifecycleScope 或 ViewModelScope 启动协程
  2. LiveData 尽量使用 Transformations.map/filter 做转换
  3. 对于不需要粘滞事件的场景,采用 SingleLiveEvent 或 Event Wrapper 设计模式
  4. 避免将 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 更适合希望多端统一、降低开发人力的团队。


成果展示与实际收益

重构完成后的半年内,我们明显感受到了几个关键改进:

  1. 可维护性大幅提升:模块之间界限清晰,修改 bug 更加精准,新人上手更快
  2. 测试覆盖率提高:ViewModel 几乎不含 Android Framework 依赖,可以用纯 Kotlin 单元测试
  3. 发布流程顺畅:因结构更清晰,提交审核被拒的情况变少了(特别是 crash 相关)
  4. 性能优化更容易:比如通过 LiveData 观察者机制减少了不必要的 UI 更新

用户体验设计-1

有一次我们遇到一个严重的内存泄漏问题,就是因为某个 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

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