ViewModel LiveData 笔记

ViewModel 不要持有 View Activity Fragment 等这些生命周期短的实例,以免造成内存泄漏。

看官方的 ViewModel 生命周期图,我们都知道 ViewModel 的生命周期和 Activity 或者 Fragment 差不多,这种理解不够严谨,因为有一个例外: 屏幕旋转的时候, Activity 会结束生命然后再重建生命,而此时 ViewModel 的生命依然存活;ViewModel 的生命存活时间大于了 Activity 的生命,该情况下,如果 ViewModel 持有了 Activity 的实例,就很容易造成内存泄漏。

ViewModel LiveData 需要注意的问题

使用 ViewModel + LiveData + Navgation 官方导航组建进行 Framgent 跳转

问题

我遇到的一个问题:从 FramgnetA navgate 跳转到 FragmentB, 然后再从 FramgentB popBackStack 或者 popBackStack 返回到 FragmentA,然后 observer 的回调执行了很多次,假设在 observer 中 set 数据到UI,这对性能,以及绘制的影响非常巨大。

原因

LiveData observe 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);

//---------------------------------
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
//---------------------------------

if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}

通过看源码,new LifecycleBoundObserver(owner, observer) 和 mObservers.putIfAbsent(observer, wrapper) FragmentA 可用时都会执行,第一次 put 的时候,做了一次保存,后续第二次进来只要 owner( FragmentA )和我们第一次存的时一致的,那么就 addObserver 通知观察者。要想自动的移除观察者,那么 owner(FramgentA) 必须先销毁。

而我在 FragmentB 返回时修改 LiveData 里面的值,我希望回到 FagmentA 显示的我返回的值,然而确实是显示了新值,还显示了很多次。再加上 FragmentA 使用 navigate 跳转到 FragmentB ,FragmentA 不会执行出栈操作,FragmentA 没有销毁,其对应的 ViewModel 也不会销毁,ViewModel 中的 LiveData 也依然存在,从而LiveData.observe 会一直存在 mObservers 这个集合中:

1
2
3
//LiveData.java
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();

最终因为 Fragment 都是一个 Fragment(LifecycleOwner),LiveData 的 observe 观察方法会不断被通知到,因为源码只针对 owenr 的异同做判断,没有对 observe 做判断,每次加载 FragmentA 都 new Observer ,然后这个 mOservers 集合就会不断 add fragmetn 对应的 observer;内部就造成了一个 owenr 对应了多个 observe 观察者。

当返回到 FragmentA 的时候,LiveData 根据这一个 owenr key 迭代取出其对应的 observer 数据,而此时 LiveData value 数据已经被修改为最新的数据,所以会造成重复取出同一个数据。

解决

在跳转到 FragmentB 之前先移除 FragmentA 下的所有 LiveData 观察,这样就不会重复 add 观察事件

1
2
LiveData.removeObservers(this)
Navigation.findNavController(activity!!, R.id.my_nav_fragment).navigate(R.id.b_fragment)

问题总结:

Fragment 不出栈,对应的 ViewModel 不会销毁,ViewModel 不销毁,ViewModel 中 LiveData 全局变量 也然存在,下次返回到该 Fragment 时,观察的依然是这同一个 LiveData,

SafeIterableMap 集合判断了 LifecycleOwner 的异同,没有判断 Observer 的异同,又因为每次返回到 FragmentA 都是 LiveData.observe(this, Observer {} 也就是每次都 new Observer,造成每次 add 的 Observer 都不一样,最终频繁通知观察者。

解决该问题两种方式:

  • 1 像上面一样先 removeObservers(Fragment)。
  • 2 想办法重新 new 一个 LiveData() ,只要不是同一个 LiveData 就不会出现上面的情况。
  • 3 使用同一个 Observer
  • 4 跳转钱让原 fragment 出栈

(略)两 Fragment 传递大型数据 - 基于 Activity 生命的共享 ViewModel