47、ViewModel与LiveData:ViewModel生命周期感知、LiveData观察数据、数据共享

说实话,ViewModel 和 LiveData 这对组合,是我在 Android 开发中用得最顺手的搭档之一。刚入行那会儿,我还在用 AsyncTask 和 Handler 处理异步数据,Activity 一旋转屏幕数据就丢了,那叫一个头疼。后来 Google 推出了 Jetpack,我才算真正解脱了。

今天咱们就聊聊这两个东西。说白了,ViewModel 帮你管数据,LiveData 帮你通知界面。它们配合起来,能让你的代码干净不少。

ViewModel:生命周期感知的数据容器

ViewModel 的核心价值是什么?我总结了一句话:它让你的数据在屏幕旋转时活下来

你想想看,Activity 重建时,里面的成员变量全没了。但 ViewModel 不会。它被存储在 ViewModelStore 里,Activity 重建后拿到的还是同一个 ViewModel 实例。

关键点:ViewModel 的生命周期比 Activity/Fragment 长。它会在 Activity 真正 finish 后才被清除。

来看一个最简单的例子:

public class MyViewModel extends ViewModel {
    private MutableLiveData<String> userName = new MutableLiveData<>();

    public LiveData<String> getUserName() {
        return userName;
    }

    public void setUserName(String name) {
        userName.setValue(name);
    }
}

在 Activity 里这样用:

MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
viewModel.getUserName().observe(this, name -> {
    textView.setText(name);
});

嗯,这里要注意:千万不要把 Context 传给 ViewModel。我见过有人把 Activity 实例传进去,结果内存泄漏了。ViewModel 的生命周期比 Activity 长,持有 Activity 引用会导致它无法被回收。

LiveData:观察者模式的优雅实现

LiveData 是个可观察的数据持有者。它跟普通的 Observable 最大的区别是什么?它知道观察者的生命周期

举个例子。你用 LiveData 在 Activity 里观察数据,当 Activity 处于 STARTED 状态时,它才会通知。如果 Activity 已经 DESTROYED,它会自动移除观察者。这省去了你手动管理注册/注销的麻烦。

我个人习惯把 LiveData 分成两种:

  • MutableLiveData:可写,只在 ViewModel 内部暴露
  • LiveData:只读,对外暴露给 View 层

这样做的好处是,View 层只能观察数据,不能修改数据。数据修改的入口统一在 ViewModel 里,逻辑清晰。

小技巧:如果你需要转换 LiveData 的数据,可以用 Transformations.map() 或 switchMap()。比如把 User 对象映射成显示用的字符串。

LiveData<String> displayName = Transformations.map(userLiveData, user -> {
    return user.getFirstName() + " " + user.getLastName();
});

数据共享:多个 Fragment 共用一个 ViewModel

这是 ViewModel 的一个杀手级应用。两个 Fragment 在同一个 Activity 里,它们可以共享同一个 ViewModel 实例。

怎么做?很简单,用 Activity 作为 ViewModelProvider 的 scope:

// Fragment A
SharedViewModel vm = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);

// Fragment B
SharedViewModel vm = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);

两个 Fragment 拿到的 vm 是同一个对象。A 修改了数据,B 通过 LiveData 观察就能收到通知。

我曾经在一个电商项目里用过这个模式。商品列表 Fragment 和购物车 Fragment 共享一个 ViewModel。用户点击加入购物车,列表 Fragment 更新 ViewModel 里的数据,购物车 Fragment 自动刷新数量。代码量少了一半,而且没有乱七八糟的接口回调。

避坑指南

我曾经踩过的坑:

  • LiveData 在 Fragment 中重复观察:Fragment 重建时,如果 onViewCreated 里又调了一次 observe,会导致回调执行多次。解决办法是用 getViewLifecycleOwner() 代替 this。
  • ViewModel 里做耗时操作:ViewModel 不能持有 Activity 引用,但可以用 Application Context。如果需要网络请求,建议结合 Repository 模式,把数据获取逻辑抽出去。
  • LiveData 的粘性事件:LiveData 在观察时会立即收到最新值。如果你只想接收后续变化,可以用 SingleLiveEvent 或 Event 包装类。

核心流程示意图

下面这张图展示了 ViewModel 和 LiveData 在 Activity 生命周期中的协作关系:

ViewModel + LiveData 生命周期协作图 Activity 创建 onCreate / onStart 活跃状态 (RESUMED) 屏幕旋转 / 重建 Activity 销毁 ViewModel 生命周期 ViewModel 创建 持有数据 (存活) 数据不变 (重建后复用) onCleared() 清除 LiveData 行为 开始观察 (observe) 活跃时通知观察者 自动恢复观察 自动移除观察者 绿色箭头:正常流程 | 红色虚线箭头:重建/销毁流程 ViewModel 在 Activity 重建时存活,LiveData 自动感知生命周期

总结一下

ViewModel 和 LiveData 解决的是 Android 开发中最常见的问题:数据与生命周期解耦。ViewModel 负责存数据,LiveData 负责通知变化。它们配合使用,能让你的代码更健壮、更易维护。

我个人建议,新项目直接上 ViewModel + LiveData,别再用那些老掉牙的方案了。你想想看,少写多少模板代码,少踩多少坑,值不值?

一句话记住:ViewModel 是数据的家,LiveData 是家的门铃。家不倒,门铃响。


公众号:蓝海资料掘金营,微信deep3321