MVVM在移动开发中的真实落地:从面试题到生产实践

Rust练习生
2026-01-15 15:18
阅读 2008

去年夏天,我还在为公司老掉牙的混合应用架构头疼。作为一家扎根成都的传统制造企业,我们的移动端团队不过五人,既要维护旧项目,又要响应老板“全面数字化”的号召。那会儿产品经理拿着竞品App跑来问:“为什么人家滑动这么丝滑?我们一加载就卡成PPT?”——我默默喝了口冰美式,心里知道:是时候和老旧的MVC说再见了。

作为一个习惯用Mac写代码、只在Windows上跑兼容测试的Java后端出身开发者,我其实对移动端一直有点“又爱又恨”。爱的是它的直接反馈和用户触达;恨的是碎片化生态和那些永远搞不定的UI适配。但随着公司开始认真做To B的移动工单系统,稳定性、可维护性和团队协作效率成了硬指标。于是,MVVM(Model-View-ViewModel)成了我们技术选型的焦点。

有意思的是,促使我深入研究MVVM的,除了工作压力,还有求职。去年底我悄悄更新了简历,刷了几轮面试题,发现几乎每家大厂都在问:“你们项目用什么架构?为什么选MVVM?和MVP有什么区别?”——这哪是面试,分明是架构设计答辩现场。于是我决定,与其背答案,不如真刀真枪干一场。


为什么是MVVM?别被概念忽悠了

很多文章一上来就讲“数据驱动”“解耦”,听起来高大上,但对我们这种小团队来说,最实际的好处是:

  • UI逻辑和业务逻辑分离:再也不用在Activity里写200行网络请求+状态判断。
  • 单元测试变得可行:以前测个登录流程得手动点半天,现在ViewModel可以直接Mock数据。
  • 新人上手快:新来的实习生三天就能改需求,不用先啃三个月祖传代码。

我们最终选择了Jetpack Compose + ViewModel + LiveData 的组合(虽然Compose还没完全普及,但团队一致认为这是未来)。后端接口用的是自家的 Spring Boot 服务,RESTful风格,返回标准JSON。前后端通过DTO对接,约定好字段命名规范,避免“前端要user_name,后端给userName”这种经典撕逼。


实战:一个工单详情页的重构

原来的工单详情页,Activity里塞了:

  • 网络请求
  • 加载状态管理
  • 弹窗逻辑
  • 权限校验
  • 本地缓存读写

代码超过800行,每次改个小需求都提心吊胆。产品经理上周五下班前说:“加个‘催办’按钮”,我差点当场表演一个原地辞职。

第一步:拆分职责

我们按MVVM重新组织:

  • Model:负责数据获取,包括网络请求(Retrofit)和本地缓存(Room)
  • ViewModel:持有UI状态,处理业务逻辑,暴露LiveData供View观察
  • View(Composable):只负责展示和用户交互,不包含任何逻辑
// ViewModel 示例
class WorkOrderDetailViewModel(
    private val repository: WorkOrderRepository
) : ViewModel() {

    private val _uiState = MutableLiveData<UiState>()
    val uiState: LiveData<UiState> = _uiState

    fun loadWorkOrder(id: String) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val order = repository.getWorkOrder(id)
                _uiState.value = UiState.Success(order)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "加载失败")
            }
        }
    }

    fun urgeCompletion(workOrderId: String) {
        viewModelScope.launch {
            repository.urgeWorkOrder(workOrderId)
            // 成功后自动刷新
            loadWorkOrder(workOrderId)
        }
    }
}

sealed class UiState {
    object Loading : UiState()
    data class Success(val order: WorkOrder) : UiState()
    data class Error(val message: String) : UiState()
}

这个设计最大的好处是:View层完全不知道数据从哪来。它只关心“当前是加载中?成功?还是错误?”,然后渲染对应UI。测试时,我们甚至可以模拟一个Error状态,看看Toast是否弹出正确。


坑与填坑:那些没人告诉你的细节

1. 生命周期陷阱

一开始我们直接在Composable里collectAsState,结果旋转屏幕时数据重复加载。后来才明白:必须用lifecycle-aware的collect。解决方案是使用repeatOnLifecycle

@Composable
fun WorkOrderScreen(viewModel: WorkOrderDetailViewModel) {
    val lifecycleOwner = LocalLifecycleOwner.current
    LaunchedEffect(viewModel) {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            viewModel.uiState.collect { state ->
                when (state) {
                    is UiState.Success -> { /* 更新UI */ }
                    is UiState.Error -> { /* 显示错误 */ }
                }
            }
        }
    }
}

2. 网络请求与Spring Boot的默契配合

后端同事用Spring Boot写的接口,返回格式统一:

{
  "code": 200,
  "data": { ... },
  "message": "success"
}

我们在Retrofit的Converter里做了全局解析,ViewModel只拿到纯净的WorkOrder对象。这样前后端各司其职,连联调会议都少开了两次——运维老哥感动得请我喝了杯瑞幸。

3. 适配问题:华为、小米、OPPO的“特色”

你以为写了Composable就一劳永逸?Too young。某些国产ROM会回收后台Activity,导致ViewModel重建。我们加了SavedStateHandle来持久化关键ID:

class WorkOrderDetailViewModel(
    savedStateHandle: SavedStateHandle,
    private val repository: WorkOrderRepository
) : ViewModel() {
    private val workOrderId = savedStateHandle.get<String>("workOrderId") ?: ""
    
    init {
        if (workOrderId.isNotEmpty()) {
            loadWorkOrder(workOrderId)
        }
    }
}

上线前,我们用Firebase Test Lab跑了20+机型,确保在千元机上也能流畅运行。用户体验不是口号,是真金白银的留存率。


性能与体验:不只是“能跑就行”

传统企业常犯的错误是:功能实现就完事。但我们这次特别关注:

  • 启动速度:ViewModel初始化延迟到真正需要时
  • 内存占用:及时取消协程,避免泄漏
  • 离线支持:Room缓存关键数据,弱网下也能查看历史工单

我们甚至加了埋点统计页面加载时长。结果发现,MVVM重构后,平均加载时间从2.1s降到0.8s——产品经理终于不再拿竞品说事了。


面试、求职与技术成长

说回开头提到的面试题。现在如果再有人问我“为什么用MVVM”,我可以自信地说:

“因为它让我们小团队在有限人力下,交付了可维护、可测试、高性能的移动应用。它不是银弹,但在我们的场景下,是最优解。”

事实上,这套架构也成了我简历上的亮点。最近有猎头联系我,聊到架构设计时,我能拿出真实数据和代码片段,而不是空谈概念。求职的本质,是证明你解决问题的能力,而不是背诵八股文。


最后一点真心话

在成都这座节奏舒服的城市,我们没有大厂的资源,也没有互联网公司的激进文化。但正因如此,每一次技术选型都更务实——不追新,只求稳;不炫技,只求效。

MVVM不是终点,而是我们迈向工程化、专业化的起点。如果你也在传统企业挣扎,不妨试试:从小模块开始,用数据说话,用结果证明。

对了,上周五的“催办”按钮,现在已经上线两周,零崩溃。我终于可以安心去太古里喝杯咖啡了。


附:MVVM vs MVP vs MVC 关键对比

维度 MVC MVP MVVM
数据流向 双向 单向(Presenter中介) 双向绑定(或单向流)
测试性 优秀(尤其配合Compose)
代码量 少(但混乱) 中等 初期多,长期维护成本低
适合团队规模 小(1-2人) 中小型 中大型或追求质量的团队
我们的结论 ❌ 已淘汰 ⚠️ 过渡方案 ✅ 主力架构

别被理论绕晕,适合你团队的,才是最好的

评论 0

最热最新
暂无评论
Rust练习生Lv.1
0
影响力
0
文章
0
粉丝