持续创作,加速成长!这是我参与「掘金日新计划 · 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()
方法来更新数据,这样也能让我们更好的理清我们的代码逻辑。
如果本文对你有任何帮助,请帮乐黎我点个赞呀,十分感谢。