使用setValue会报错,那就一直使用postValue行不行?

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

这篇文章是对LiveData更新数据方法 setValue()postValue()的一个思考,不知道屏幕前的你有没有这样相同的疑惑?

postValue 与 setValue 有何区别,怎么用?

LiveData的官方文档中有这么介绍:您必须调用 [setValue(T)](<https://developer.android.com/reference/androidx/lifecycle/MutableLiveData#setValue(T)>) 方法以从主线程更新 LiveData 对象。如果在工作器线程中执行代码,您可以改用 [postValue(T)](<https://developer.android.com/reference/androidx/lifecycle/MutableLiveData#postValue(T)>) 方法来更新 LiveData 对象。

也就是说这两个方法的主要区别就是,一个是工作在主线程的,而另一个是工作在子线程。

分别看看他们的源码:

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

assertMainThread("setValue")方法就是用来判断当前线程是否处于主线程,如果不是,会抛出异常"Cannot invoke setValue on a background thread",也就是不能再子线程中调用setValue()方法。

如果处于主线程中,就将当前设置的最新数据分发给各个观察者对象。

接着再来看看postValue()方法的源码:

final Object mDataLock = new Object();

private volatile Object mData;
volatile Object mPendingData = NOT_SET;

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
复制代码

只要涉及到子线程,就会存在线程安全问题,postValue()也不例外。所以这里出现了synchronized (mDataLock) ,锁住 mDataLock 对象,并且使用 volatile 关键字来修饰 mPendingData 待发送数据对象,保证线程间的及时可见性,从而来保证线程安全。

而且从源码中也可以看出,postValue()本质上也是通过setValue()来更新数据的,通过切换到主线程来执行setValue()

一直用postValue()行不行?

既然postValue()本质上也是通过setValue()来更新数据的,而且当你不小心在子线程中使用setValue()来更新数据时,会抛出异常,那这样的话,我可不可以一直使用postValue()来更新数据呢?

来个简单例子跑一下:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    ...省略代码...

    dbinding.apply {

        setValueBtn.setOnClickListener {
            for (setValueNum in 0..100) {
                Log.e("LiTest", "onCreate: setValueNum = $setValueNum")
                GlobalState.testNumberLd.value = setValueNum
            }
        }

        postValueBtn.setOnClickListener {
            for (postValueNum in 0..100) {
                Log.e("LiTest", "onCreate: postValueNum = $postValueNum")
                GlobalState.testNumberLd.postValue(postValueNum)
            }
        }

        GlobalState.testNumberLd.observe(this@TestHelperActivity, {
            Log.e("LiTest", "********* onCreate: observe number = $it", )
        })
    }

}
复制代码

我们先建一个Activity,放两个按钮,分别是setValueBtn按钮,用来调用setValue()方法,以及postValueBtn按钮,用来调用postValue()方法。点击按钮,分别进行100次数据更新。

思考一下,我们的观察者会分别观察到怎样的数据情况呢?

看看setValue()方法输出的日志情况:

E/LiTest: onCreate: setValueNum = 0
E/LiTest: ********* onCreate: observe number = 0
E/LiTest: onCreate: setValueNum = 1
E/LiTest: ********* onCreate: observe number = 1
E/LiTest: onCreate: setValueNum = 2
E/LiTest: ********* onCreate: observe number = 2
···
···
E/LiTest: onCreate: setValueNum = 99
E/LiTest: ********* onCreate: observe number = 99
E/LiTest: onCreate: setValueNum = 100
E/LiTest: ********* onCreate: observe number = 100
复制代码

每次进行setValue()更新数据后,我们的观察者都能接收到最新的数据。

再来看看postValue()方法输出的日志情况:

E/LiTest: onCreate: postValueNum = 0
E/LiTest: onCreate: postValueNum = 1
E/LiTest: onCreate: postValueNum = 2
···
···
E/LiTest: onCreate: postValueNum = 99
E/LiTest: onCreate: postValueNum = 100
E/LiTest: ********* onCreate: observe number = 100
复制代码

从日志可以看出,即使我们进行了100次postValue(),最终我们的观察者对象只观察到最后一次postValue()更新的数据。

这是当我们使用postValue()连续更新数据时,只有最后的数据会被更新,例如:

liveData.postValue("a");
liveData.postValue("b");
liveData.postValue("c");
复制代码

最终,活跃的观察者对象们只会收到数据"c",而感知不到"a""b"

总结

虽然我们可以一直使用postValue()方法来更新数据,它不像setValue()方法会抛出异常,所以就开发的难易程度来说,一直使用postValue()来更新数据会更加的简单。但是postValue()会丢失数据,而且它需要进行切换到主线程来调用setValue()方法来更新数据,所以就性能来说,肯定会造成更多的开销。所以我们不能一直使用postValue()方法来更新数据,而是需要合理的在主线程通过setValue()方法来更新数据,在子线程中通过postValue()方法来更新数据,这样也能让我们更好的理清我们的代码逻辑。

如果本文对你有任何帮助,请帮乐黎我点个赞呀,十分感谢。

猜你喜欢

转载自juejin.im/post/7101670008350048286