LiveData、ViewModel

ViewModel

优点

  • 职责分离:让UI组件(Activity/Fragment)专注于数据展示,而数据获取和状态管理由ViewModel负责。
  • 状态保留:在配置更改(如旋转屏幕)时自动保留数据,无需开发者手动处理。但是当用户按返回键退出 Activity 或系统回收资源时, ViewModel 会随 Activity 一起销毁
  • 数据共享:同一Activity内的多个Fragment可通过ViewModel轻松共享数据。如果多个 activity 共享的话创建 Application 级别的 ViewModel,不推荐
  • 资源清理:通过重写onCleared()可集中释放资源,避免内存泄漏。
  • 测试友好:分离的业务逻辑使单元测试更简单、更聚焦。

MvvM封装

原理

UserModel model = ViewModelProviders.of (this). get (UserModel. class);
  • 使用 ViewModelProvider 的 get()方法来获取作为参数传入的 ViewModel 类型的实例。 
  • 先从尝试从ViewModelStore 取,ViewModel 存储在 ViewModelStore 的 HasHmap 中
  • 如果没取到,通过ViewModelProviders create 反射创建 ViewModel
  • 最近把创建的 ViewModel 保存在 ViewModelStore 里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
String key = DEFAULT_KEY + ":" + canonicalName;

// 1. 尝试从缓存获取
ViewModel viewModel = mViewModelStore.get(key);

if (modelClass.isInstance(viewModel)) {
return (T) viewModel;
}

// 2. 通过Factory创建新实例
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) mFactory).create(key, modelClass);
} else {
viewModel = mFactory.create(modelClass);
}

// 3. 存储并返回
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}

ViewModelProvider 传 activity 的原因是
没有绑定 activity 的生命周期。

  • 每个 activity 都有一个 ViewModelStore,ViewModelStore 里 HashMap<String, ViewModel> mMap,key 是自己 viewmodel 的名字,值是自己 viewmodel 的 class 类。

clear 什么时候执行

  • 在 ComponentActivity 创建的时候,ViewModelStore 进行了生命周期的绑定工作, onDestroy () ,遍历调用所有的 ViewMode调用了 clear () 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// 只有是正常的onDestory才能clear掉ViewModelStore
if (!isChangingConfigurations()) {
// 执行流程ViewmodelStore.clear --> ViewModel.clear --> ViewModel.OnClear
getViewModelStore().clear();
}
}
}
});

ViewModelStore
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}

没有持有 view 的引用,要不然用 livedata 干啥
ViewModel 不应该直接引用 Views 的原因; 它们可以比 View 的生命周期更长久
ViewModel 不能持有 activity,fragment 等 view 的引用,避免内存泄漏。他可以获取到 application 的 context

  • 不要持有 UI 引用:class LeakyViewModel(activity: Activity) : ViewModel()
  • 多个 Activity 或 Fragment 之间的数据共享: 当需要在多个 Activity 或 Fragment 之间共享数据时,使用 ViewModel 可以方便地实现这目标。但是,需要注意传递数据的正确性,避免出现数据不一致的情况。
  • 状态保存和恢复: 在使用 ViewModel 时,需要注意保存和恢复数据的状态。如果数据丢失,可能会导致用户界面出现问题。因此,需要适当地使用 ViewModel 来保存和恢复数据。

数据恢复

2.0之后,其实是用到了 Activity 的 onRetainNonConfigurationInstance () 和 getLastNonConfigurationInstance () 这两个方法
在横竖屏切换 onRetainNonConfigurationInstance 保存 ViewModel 的实例,然后 getViewModelStore 发现有保存的就恢复

保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final Object onRetainNonConfigurationInstance() {
// 1. 获取自定义保留对象(向后兼容旧API)
Object custom = onRetainCustomNonConfigurationInstance();

// 2. 获取当前ViewModelStore
ViewModelStore viewModelStore = mViewModelStore;

// 3. 处理尚未初始化的ViewModelStore情况
if (viewModelStore == null) {
// 尝试从上次配置变更的保留实例中获取
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}

// 4. 判断是否需要创建保留实例
if (viewModelStore == null && custom == null) {
return null; // 无需保留任何内容
}

// 5. 创建新的保留实例容器
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;

return nci; // 返回保留的实例
}

恢复

ComponentActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在 getViewModelStore() 方法中
public ViewModelStore getViewModelStore() {
if (mViewModelStore == null) {
// 尝试从保留实例恢复
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore; // 关键恢复点
}
// 如果没有则创建新的
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}

ViewModel 还提供了以下方法来帮助开发者保存状态:

  • **onSaveInstanceState ()**:该方法用于保存 ViewModel 的临时状态数据。
  • **onRestoreInstanceState ()**:该方法用于恢复 ViewModel 的临时状态数据。

SavedStateHandle

SavedStateHandle 是一个类,它用于保存 ViewModel 的状态数据。SavedStateHandle 可以保存任何可序列化类型的数据,例如基本类型、String、List、Map 等。
需要开发者手动调用 savedStateHandle.set() 方法进行保存。
原因如下:

  • 灵活性: 自动保存所有数据可能会导致不必要的开销,因为并非所有数据都需要持久保存。例如,一些临时数据可能只在当前界面中使用,不需要保存到 Bundle 中。
  • 控制力: 手动保存数据可以让开发者更好地控制保存哪些数据以及何时保存数据。例如,开发者可以选择在特定条件下才保存数据,或者只保存部分数据。

LiveData

一句话概括 LiveData:LiveData 是可感知生命周期的,可观察的数据持有者。作用就是更新 UI。
它有一些可以被认为是优点的特性:

  • 观察者的回调永远发生在主线程 也是缺点,setValue 主线程中调用,子线程 postValue。
  • 仅持有单个且最新的数据
  • 自动取消订阅
  • 提供「可读可写」和「仅可读」两个版本收缩权限
  • 配合 DataBinding 实现「双向绑定」

不做跟风党,LiveData,StateFlow,SharedFlow 使用场景对比 - 掘金

监听 Acitivty 生命周期可以这样写
实现:DefaultLifecycleObserver
注册:getLifecycle (). addObserver (GpsManager. getInstance)

LiveData 是一个可被观察的数据持有者类,它只有在 STARTED 或者 RESUMED 状态时才会被激活,在 DESTROYED 状态时,会自动 removeObserver ()
有一种情况下,不会自动 removeObserver ():当你调用 observeForever ()方法的时候,你需要手动去调用 removeObserver()方法。

LiveData 的优点

LiveData 的优点是具备生命周期感知,自动管理 Observer,避免内存泄漏。

防泄漏:当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
防崩溃:当 Activity 处于后台状态时,是不会收到 LiveData 的任何事件的。当某个页面请求网络数据成功后需要同步 UI, 但这个页面已经不可见, 这时就会停止同步 UI 的操作

一条消息能被多个观察者消费,多个页面公用 liveDta, 实现多个页面数据的同时监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PlayLiveData {

/**
* 当前播放进度
*/
val progressLiveData = MutableLiveData<ProgressBean>()

/**
* 播放模式
*/
val playModeLiveData = MutableLiveData<Int>()

/**
* 重置
*/
val resetLiveData = MutableLiveData<Int>()

}
PlayerManager: val playLiveData = PlayLiveData ()

PlayerManager.instance.playLiveData.progressLiveData.observe (this, Observer {
playVM?.setProgress (it)
})

需要生命周期的工具类,更加内聚

是让人耳目一新的 Jetpack MVVM 精讲啊!

unknown_filename.4|700

LiveData 的缺点

  • LiveData 只能在主线程更新数据: 只能在主线程 setValue,即使 postValue 内部也是切换到主线程执行
  • LiveData 不防抖: 重复 setValue 相同的值,订阅者会收到多次 onChanged () 回调(可以使用 distinctUntilChanged () 解决,此处不展开);
  • LiveData 不支持背压: 在数据生产速度 > 数据消费速度时,中间的数据会忽略。比如在子线程大量 postValue 数据但主线程消费跟不上时,中间就会有一部分数据被忽略。
  • LiveData 数据重放问题(粘性): 注册新的订阅者,会重新收到 LiveData 存储的数据,这在有些情况下不符合预期(可以使用自定义的 LiveData 子类 SingleLiveData 或 UnPeekLiveData 解决)

不防抖

setValue()/postValue() 传入相同的值多次调用,观察者的 onChanged() 会被多次调用。

严格讲这不算一个问题,看具体的业务场景,处理也很容易,官方在 Transformations 中提供了 distinctUntilChanged() 方法,配合官方提供的扩展函数,如下使用即可:

1
2
3
4
5
6
7
override fun onViewCreated (view: View, savedInstanceState: Bundle?) {
super.onViewCreated (view, savedInstanceState)

viewModel.headerText.distinctUntilChanged().observe(viewLifecycleOwner) {
header.text = it
}
}

粘性事件

如何优雅的使用LiveData实现一套EventBus(事件总线) - 简书

设计成了黏性事件,发送事件后在订阅也可以收到消息。
原因:LiveData setValue 的时候里面有个 mVersion,然后+1,当观察者注册时(considerNotify()) 会判断,观察者的 mLastVersion 默认为 -1,满足 mLastVersion < mVersion 条件,发现版本号落后,立即调用 onChanged() ,所以订阅者会马上收到订阅之前发布的最新消息

Android 使用 LiveData 实现 EventBus - 掘金

1
2
3
4
5
6
7
8
9
@MainThread
protected void setValue (T value) {
assertMainThread ("setValue");
// 发送版本+1
mVersion++;
mData = value;
// 信息分发
dispatchingValue (null);
}

记住这里的 mVersion,它本问题关键,每次更新数据都会自增,默认值是 -1。然后我们跟进下 dispatchingValue () 方法:

mDispatchingValue 的判断主要是为了解决并发调用 dispatchingValue 的情况,当对应数据的观察者在执行的过程中, 如有新的数据变更, 则不会再次通知到观察者,所以观察者内的执行不应进行耗时工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void dispatchingValue (@Nullable ObserverWrapper initiator) {

if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
// 这里
considerNotify (initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions (); iterator.hasNext (); ) {
// 这里
considerNotify (iterator.next (). getValue ());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}


可以看到,无论条件判断,最终都会执行 considerNotify () 方法,所以我们继续跟进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void considerNotify (ObserverWrapper observer) {
if (! observer. mActive) {
return;
}
if (! observer.shouldBeActive ()) {
observer.activeStateChanged (false);
return;
}
// 判断 version
if (observer. mLastVersion >= mVersion) {
return;
}
observer. mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged ((T) mData);
}

终于到了最关键的时候了!!如果 ObserverWrapper 的 mLastVersion 小于 LiveData 的 mVersion,那么就会执行的 onChange () 方法去通知观察者数据已更新。

而 ObserverWrapper. mLastVersion 的默认值是 -1, LiveData 只要更新过数据,mVersion 就肯定会大于 -1,所以订阅者会马上收到订阅之前发布的最新消息!!

解决

  1. 反射:Android 消息总线的演进之路:用 LiveDataBus 替代 RxBus、EventBus 使用的反射的方式修改 LiveData 中的 mVersion 值去实现。
  2. 包装类: 基于 LiveData 实现事件总线思路和方案,此方案是基于自定义观察者包装类,因为粘性消息最终会调用到 Observer onChange () 方法,因此我们自定义 Observer 包装类,自己维护实际的订阅消息数,来判断是否需要触发真正的 onChange () 方法。
包装类

自定义 Observer,mLastVersion 默认就直接等于 LiveData 的 version

1
2
3
4
5
6
7
8
9
10
11
internal open class ExternalObserverWrapper<T>(val observer: Observer<in T>, val liveData: ExternalLiveData<T>): Observer<T>{
// 新建观察者包装类的时候, 内部实际的 version 直接等于 LiveData 的 version
private var mLastVersion = liveData. version
override fun onChanged (t: T) {
if (mLastVersion >= liveData. version){
return
}
mLastVersion = liveData. version
observer.onChanged (t)
}
}

只接收一次

如果在多线程中同一个时刻,多次调用了 postValue () 方法,==只有最后次调用的值会得到更新==。也就是此方法是有可能会丢失事件!!

设计原因:为了兼顾性能,UI 只需显示最终状态即可,省略中间态造成的频发刷新。这或许是设计目的之一,但是一个更为合理的解释是:即使 post 多次也没有意义,所以只 post 一次即可

源码:

  1. postValue 只是把传进来的数据先存到 mPendingData,不保留中间值,后写入的值直接覆盖前值
  2. 当无待处理值时创建新任务,才 post 数据

解决方案:重写 postValue 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private final Runnable mPostValueRunnable = new Runnable () {
@Override
public void run () {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
// 设置 mPendingData 为 not_set,重置状态
mPendingData = NOT_SET;
}
setValue ((T) newValue);
}
};

protected void postValue (T value) {
boolean postTask;
synchronized (mDataLock) {
// 判断 postTask 是否为 not_set
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (! postTask) {
return;
}
ArchTaskExecutor.getInstance (). postToMainThread (mPostValueRunnable);
}

LiveData 源码

LiveData 本质上是一个带生命周期感知的观察者容器
源码里通过 ObserverWrapper 包装 Observer,每个 Observer 都有一个 active 状态和 lastVersion

当调用 observe 时,如果是绑定 LifecycleOwner,就用 LifecycleBoundObserver 监听生命周期,只有在 STARTED / RESUMED 状态才会变成 active。页面 DESTROYED 时会自动移除 Observer,避免内存泄漏。

数据更新时,setValue 会递增版本号 mVersion,分发时只有 active 且 lastVersion 小于当前 version 的 Observer 才会收到回调,从而避免重复分发。

postValue 本质是切到主线程再调用 setValue,多次 post 只保留最后一次。

总结就是:LiveData 通过生命周期控制 + 版本号机制,保证只在合适的时机、安全地通知 UI

  • 为什么不会内存泄漏?
    DESTROYED 时自动 removeObserver

  • 是不是粘性?
    新 Observer active 时会收到最新一次数据,但不会收到历史多次

  • 和 EventBus 最大区别?
    LiveData 有生命周期感知,EventBus 没有

unknown_filename.2|800
setValue 里会执行 Observer 的 onChanged 方法

1
2
3
public interface Observer<T> {
void onChanged (T t);
}

有同学提出,我如果希望这种情况下,Activity 在后台依然能够响应数据的变更,可不可以呢?当然可以,LiveData 此外还提供了 observerForever () 方法,在这种情况下,它能够响应到任何生命周期中数据的变更事件
https://juejin.cn/post/6844903748574117901
unknown_filename.3|700

LifecycleOwner

LifecycleOwner 是一个接口,是一个生命周期感知组件能够感知 Activity、 Fragment 等组件的生命周期变化,并将变化通知到已注册的观察者。LifecycleOwner 之所以设计成接口,是为了其它对象可以使用到,这样其它对象就无需要求 Activity、Fragment 在特定的生命周期中调用特定的方法,比如终结方法、暂停方法,而这些要求往往可能被程序员所忽略,也使 Activity、Fragment 变得臃肿复杂。

业务层逻辑更加内聚,无需依赖 UI 去做生命周期相关阶段的处理,避免出错

真正有生命周期的是 lifrcycle

1
2
3
4
5
public interface LifecycleOwner {
    @NonNull
    Lifecycle getLifecycle ();
}

原理:接口,在 Activity、Fragment 生命周期的方法里调用 Lifecycle 接口各自的方法

LifecycleOwner 观察它
LifecycleObserver 观察者

可以通过被 LifecycleOwner 类的 addObserver (LifecycleObserver o)方法注册, 被注册后,LifecycleObserver 便可以观察到 LifecycleOwner 的生命周期事件。

当一个应用程序实现了 Application. ActivityLifecycleCallbacks 接口时,它可以注册一个监听器来监控和响应应用程序中活动(Activity)的生命周期事件。通过实现这个接口,应用程序可以接收有关活动的创建、启动、暂停、恢复和销毁等生命周期事件的通知。

场景:看前后台 implements Application.ActivityLifecycleCallbacks

讲的不错
引入Jetpack架构后,你的App会发生哪些变化?上篇文章我给大家分享了我对架构的理解,从思想层面去讲述架构的演进过程 - 掘金

VoiceViewManager DefaultLifecycleObserver
使用前需要在当前页面注册监听 lifecycle.addObserver (VoiceViewManager)


其他

有些时候我们从 repository 层拿到的数据需要进行处理,例如从数据库获得 User List,我们想根据 id 获取某个 User。

此时我们可以借助 MediatorLiveData 和 Transformatoins 来实现:

1
2
3
4
5
class MainViewModel {
val viewModelResult = Transformations.map (repository.getDataForUser ()) { data ->
convertDataToMainUIModel (data)
}
}

LiveData、ViewModel
http://peiniwan.github.io/2025/12/242ac5540d80.html
作者
六月的雨
发布于
2025年12月16日
许可协议