MVVM在移动开发中的真实落地:一个传统Java程序员的实战复盘

红黑树下乘凉
2026-01-05 07:14
阅读 1729

上周五晚上十点半,我还在公司改一个Android页面的崩溃Bug——又是典型的“数据变了但UI没刷新”。产品经理站在身后幽幽地说:“这个需求双11前必须上线啊,用户反馈说加载慢得像PPT。”我一边点头一边心里嘀咕:你们改需求的速度比我们刷LeetCode还快。

作为一家传统制造业企业的“数字化先锋”(其实就是被拉来写App的老后端),我平时主要用Spring Boot写业务接口,但去年公司搞“全面移动化”,硬是把我塞进了移动端小组。虽然嘴上喊着“我是Java后端,不懂前端那一套”,实际上早就偷偷啃完了《Android开发艺术探索》和《第一行代码》,最近还在边刷面试题边研究Jetpack组件——毕竟嘛,谁不想跳槽去大厂呢?

今天这篇,就聊聊我在项目中落地MVVM架构的真实经历。不是教科书式的理论堆砌,而是实打实的“踩坑-填坑-再踩坑”循环,希望能给同样在传统企业挣扎求生的兄弟们一点参考。

为什么非得用MVVM?

一开始我们团队用的是最原始的MVC:Activity里又查数据库又发网络请求又更新UI,代码长得像意大利面条。每次改个字段都要翻半天逻辑,测试一跑就崩,运维看了直摇头。

后来领导说要“对标互联网公司”,于是我们决定重构。选型时纠结过MVP和MVVM。MVP虽然解耦清晰,但Presenter层代码量爆炸,而且生命周期管理容易出问题。而MVVM配合LiveData + ViewModel,能自动处理生命周期,UI和数据绑定也更直观——尤其适合我们这种既要快速迭代又要保证稳定性的场景。

最关键的是,面试官最近老问Jetpack相关的问题。我刷牛客网的时候发现,80%的Android岗JD都写着“熟悉MVVM架构”,这不学不行啊!

实战:从混乱到有序

我们的核心模块是一个设备状态监控页,实时显示产线机器的运行数据。旧版代码长这样:

// Activity里直接调Retrofit + Handler更新UI
public void loadMachineData() {
    api.getMachineStatus().enqueue(new Callback<>() {
        @Override
        public void onResponse(...) {
            runOnUiThread(() -> {
                textView.setText(data.status);
                progressBar.setVisibility(GONE);
            });
        }
    });
}

问题很明显:网络回调和UI强耦合,旋转屏幕就内存泄漏,测试根本没法写。

第一步:引入ViewModel + LiveData

我们新建了一个MachineViewModel

class MachineViewModel : ViewModel() {
    private val _status = MutableLiveData<String>()
    val status: LiveData<String> = _status

    fun loadStatus() {
        // 使用协程替代Callback地狱
        viewModelScope.launch {
            try {
                val data = repository.fetchMachineStatus()
                _status.value = data.status
            } catch (e: Exception) {
                _status.value = "加载失败"
            }
        }
    }
}

然后在Activity里观察:

override fun onCreate(savedInstanceState: Bundle?) {
    viewModel.status.observe(this) { status ->
        binding.statusText.text = status
    }
    viewModel.loadStatus()
}

效果立竿见影:旋转屏幕不再重复请求,内存泄漏没了,单元测试也能Mock ViewModel了。

第二步:数据绑定(DataBinding)提升开发效率

虽然有人说ViewBinding更轻量,但我们还是选了DataBinding——因为可以直接在XML里写表达式,省掉大量findViewById和setText样板代码。

<!-- layout/machine_status.xml -->
<layout>
    <data>
        <variable name="vm" type="com.example.MachineViewModel"/>
    </data>
    <TextView
        android:text="@{vm.status}"
        android:visibility="@{vm.loading ? View.GONE : View.VISIBLE}" />
</layout>

不过这里有个坑:DataBinding在低端机上性能略差。我们线上监控发现,部分千元机首次渲染延迟高达300ms。解决方案是只在复杂列表外层用DataBinding,内部Item用ViewBinding,平衡开发效率和性能。

第三步:统一Repository层,屏蔽数据源差异

以前网络、本地缓存、数据库各写各的,现在统一收口到Repository:

class MachineRepository {
    suspend fun fetchMachineStatus(): MachineData {
        // 先查Room数据库
        val local = dao.getLatest()
        if (local != null && !isExpired(local)) {
            return local
        }
        // 再走网络
        val remote = api.getMachineStatus()
        // 存入数据库
        dao.insert(remote)
        return remote
    }
}

这一招让我们轻松实现了“有网用最新数据,无网用缓存”的体验,产品经理终于没再抱怨“离厂区就没信号看不了设备状态”了。

跨平台适配:别忘了iOS兄弟

虽然我是Android出身,但公司同时在做iOS版。我们约定ViewModel层的业务逻辑尽量对齐,比如状态字段命名、错误码定义。虽然不能共享代码,但至少文档和接口一致,联调时少了很多“你那边怎么又改了?”的扯皮。

另外,Google Play和国内应用市场审核规则不同。我们曾因使用了某个权限被拒,后来在MVVM的UseCase层加了权限检查开关,动态控制功能可见性,顺利过审。

性能与体验:不能只谈架构

MVVM不是银弹。我们曾过度使用LiveData导致Observer太多,页面卡顿。后来做了两件事:

  1. 合并相关状态到一个UiState data class,减少Observer数量
  2. 对高频更新的数据(如传感器数值)改用Flow + StateFlow,避免主线程阻塞
方案 首屏渲染(ms) 内存占用(MB) 单元测试覆盖率
旧MVC 850 45 12%
MVVM+DataBinding 620 38 67%
MVVM+ViewBinding 580 35 71%

数据不会骗人。重构后Crash率下降60%,连测试同事都说“终于不用天天提同款Bug了”。

给想跳槽的同学一点建议

如果你也在准备求职,务必把MVVM吃透。我最近面了几家公司,几乎都会问:

  • “ViewModel如何感知生命周期?”
  • “LiveData和StateFlow的区别?”
  • “如何避免ViewModel内存泄漏?”

这些问题光背答案不够,得结合项目讲清楚。比如我就说了我们怎么用viewModelScope自动取消协程,面试官眼睛都亮了。

推荐两本书:《Android编程权威指南》打基础,《Kotlin实战》学协程。别光刷题,动手重构一个小模块,写进简历里比“熟悉MVVM”有力得多。

最后几句真心话

在传统企业搞技术革新,真的不容易。没有大厂的资源,却要扛住业务压力;没有专职前端,却要做出丝滑体验。但正是这种“既要又要还要”的环境,逼着我们把每个技术点都抠到极致。

MVVM不是终点,而是起点。现在我们已经在探索Compose + MVVM的新组合,虽然领导说“能跑就行别折腾”,但我知道,只有不断进化,才能在下一次跳槽时,笑着说出那句:“我主导了XX万DAU App的架构升级。”

共勉吧,打工人。

评论 0

最热最新
暂无评论
红黑树下乘凉Lv.1
0
影响力
0
文章
0
粉丝