随笔博文

为什么LiveData的观察者必须处于主线程中

2022-12-20 12:41:36 michael007js 155

前言

在我的上一篇*为什么Google要将LiveData设计成粘性的*文章中,有一位爱思考的小伙伴留下了这么一个评论。

截屏2022-06-07 下午2.23.48.png

在看到这个评论后,我产生了两个疑问?

  1. LiveData的观察者对象必须得处于主线程中吗?

  2. 究竟是什么原因让Google这么设计,正是这位小伙伴所述的原因吗?

由此有了这篇文章,再次感谢这位小伙伴的留言探讨。

观察者对象必须得处于主线程中吗?

先说答案:

是的,LiveData的观察者对象必须处于主线程中。

找到这个答案的方法也很简单,我们直接查看LiveData源码中的注册观察者方法即可。

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
   assertMainThread("observe");
 ...省略代码...
}

@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
   assertMainThread("observeForever");
 ...省略代码...
}

@MainThread
public void removeObserver(@NonNull final Observer<? super T> observer) {
   assertMainThread("removeObserver");
 ...省略代码...
}

@MainThread
public void removeObservers(@NonNull final LifecycleOwner owner) {
   assertMainThread("removeObservers");
 ...省略代码...
}

不管是注册观察者方法还是注销观察者方法中,都使用了assertMainThread()方法来保证方法处于主线程中,如果方法处于子线程中就会直接抛出异常。

OK,知道了答案,接着就是下一个为什么了。

为什么观察者必须处于主线程中

这里我们采用反证法,假设我们的观察者对象是可以处于子线程中的,那会发生什么?

假设现在我们有多个子线程,且它们引用了同一个Observer,这时LiveData持有的数据发生了更新,需要将该最新数据通知到所有处于活跃状态的观察者对象。

如果你熟悉LiveData的源码,那么你肯定知道,是否通知观察者对象的判断依据正是通过observer.mLastVersionLiveData.mVersion版本对比。

private void considerNotify(ObserverWrapper observer) {
 ...省略代码...
   if (observer.mLastVersion >= mVersion) {
       return;
 }
   observer.mLastVersion = mVersion;
   observer.mObserver.onChanged((T) mData);
}

那此时,如果我们的观察者对象是处于多线程并发环境下,也就是 considerNotify方法是处于并发环境下,此刻是无法保证线程安全的。从而mLastVersion无法保证内存及时可见性,从而造成与LiveDatamVersion对比出现问题,结果就会造成某些子线程中的观察者无法接收到数据更新的通知。

所以说,我们应该在保证线程安全下进行observer.mLastVersionLiveData.mVersion的版本比对工作。

那如何来保证线程安全呢?

那Google是怎么保证线程安全呢?

从代码中,我们可以发现considerNotify方法是一个私有函数,所以我们从considerNotify方法开始往上推,可以发现considerNotify方法只是被dispatchingValue 方法所调用,那我们就需要具体看看哪里调用了dispatchingValue 方法。

查看LiveData源码,我们可以发现只在两个地方调用到,分别是:

  1. setValue()方法中被调用。

  2. ObserverWrapper.activeStateChanged()方法中被调用。

@MainThread
protected void setValue(T value) {
   assertMainThread("setValue");
   mVersion++;
   mData = value;
   dispatchingValue(null);
}

private abstract class ObserverWrapper {
 ......

   void activeStateChanged(boolean newActive) {
       if (newActive == mActive) {
           return;
     }
       // immediately set active state, so we'd never dispatch anything to inactive
       // owner
       mActive = newActive;
       changeActiveCounter(mActive ? 1 : -1);
       if (mActive) {
           dispatchingValue(this);
     }
 }
}

我们知道LiveData会在数据发生更改去通知所有处于活跃状态的观察者对象以及在某个观察者对象生命周期状态发生更改时去通知它,而这就体现在这两个方法中。

首先,setValue()方法相信大家都很熟悉,我们必须在主线程中进行调用,否则会抛出异常。在代码中体现出来的,正是通过assertMainThread("setValue");来保证方法处于主线程中。

那Google是如何保证ObserverWrapper.activeStateChanged() 方法中的 dispatchingValue 方法是处于主线程的呢?

答:直接让整个ObserverWrapper处于主线程中,也就是直接让我们的观察者对象处于主线程中,从而来保证线程安全。

为什么不用Synchronized + Volatile来保证线程安全呢?

不知你有没有同样的疑问: 为什么不用 Synchronized + Volatile 关键字来保证该方法线程安全呢?就像postValue()方法那样。那这样我们就可以在子线程中注册观察者对象了。

从实际业务出发来思考: 在实际业务开发中,我们基本上都是拿到LiveData中的最新数据,然后更新UI。而这使用场景也决定我们的观察者对象只需处于主线程中即可,不需要处于子线程中。这样一来,Google直接规定观察者对象必须处于主线程中,就可以很好的保证线程安全。相对比采用Synchronized + Volatile 关键字来保证线程安全,更加的简单高效。

当然这只是我的一个观点,欢迎大家留言与我一同探讨。你们有遇到过需要在子线程中注册观察者的使用场景吗?

总结

本文是基于小伙伴的留言来展开思考,通过本文,我们知道LiveData的观察者对象必须得处于主线程中,因为我们应该在保证线程安全下进行observer.mLastVersionLiveData.mVersion的版本比对工作。相对比postValue()方法采用Synchronized + Volatile关键字来保证线程安全,这里Google直接通过规定观察者对象必须处于主线程中来保证线程安全,十分简单高效。


首页
关于博主
我的博客
搜索