众所周知,程序员面试的时候,很多面试官喜欢会就一个问题不断深入追问。
例如一个小小的 LiveData 的 postValue,就可能会问出一连串问题:
postValue 与 setValue
postValue
与 setValue
一样都是用来更新 LiveData 数据的方法:
- setValue 只能在主线程调用,同步更新数据
- postValue 可在后台线程调用,其内部会切换到主线程调用 setValue
liveData.postValue("a");
liveData.setValue("b");
上面代码,a 在 b 之后才被更新。
postValue 收不到通知
postValue 使用不当,可能发生接收到数据变更的通知:
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
如上,源码的注释中明确记载了,当连续调用 postValue 时,有可能只会收到最后一次数据更新通知。
梳理源码可以了解其中原由:
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
mPendingData
被成功赋值 value 后,post 了一个 Runnable
mPostValueRunnable
的实现如下:
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);
}
};
- postValue 将数据存入
mPendingData
,mPostValueRunnable
在UI线程消费mPendingData
。 - 在 Runnable 中
mPendingData
值还没有被消费之前,即使连续 postValue , 也不会 post 新的 Runnable -
mPendingData
的生产 (赋值) 和消费(赋 NOT_SET) 需要加锁
这也就是当连续 postValue 时只会收到最后一次通知的原因。
源码梳理过了,但是为什么要这样设计呢?
为什么 Runnable 只 post 一次?
当 mPenddingData
中有数据不断更新时,为什么 Runnable 不是每次都 post,而是等待到最后只 post 一次?
一种理解是为了兼顾性能,UI只需显示最终状态即可,省略中间态造成的频发刷新。这或许是设计目的之一,但是一个更为合理的解释是:即使 post 多次也没有意义,所以只 post 一次即可
我们知道,对于 setValue 来说,连续调用多次,数据会依次更新:
如下,订阅方一次收到 a b 的通知
liveData.setValue("a");
liveData.setValue("b");
通过源码可知,dispatchingValue()
中同步调用 Observer#onChanged()
,依次通知订阅方:
//setValue源码
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
但对于 postValue,如果当 value 变化时,我们立即post,而不进行阻塞
protected void postValue(T value) {
mPendingData = value;
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
public void run() {
setValue((T) mPendingData);
}
};
liveData.postValue("a")
liveData.postValue("b")
由于线程切换的开销,连续调用 postValue,收到通知只能是b、b,无法收到a。
因此,post 多次已无意义,一次即可。
为什么要加读写锁?
前面已经知道,是否 post 取决于对 mPendingData 的判断(是否为 NOT_SET)。因为要在多线程环境中访问 mPendingData ,不加读写锁无法保证其线程安全。
protected void postValue(T value) {
boolean postTask = mPendingData == NOT_SET; // --1
mPendingData = value; // --2
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
public void run() {
Object newValue = mPendingData;
mPendingData = NOT_SET; // --3
setValue((T) newValue);
}
};
如上,如果在 1 和 2 之间,执行了 3,则 2 中设置的值将无法得到更新
使用RxJava替换LiveData
如何避免在多线程环境下不漏掉任何一个通知? 比较好的思路是借助 RxJava 这样的流式框架,任何数据更新都以数据流的形式发射出来,这样就不会丢失了。
fun Observable.toLiveData(): LiveData = RxLiveData(this)
class RxLiveData(
private val observable: Observable
) : LiveData() {
private var disposable: Disposable? = null
override fun onActive() {
disposable = observable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
setValue(it)
}, {
setValue(null)
})
}
override fun onInactive() {
disposable?.dispose()
}
}
最后
想要保证事件在线程切换过程中的顺序性和完整性,需要使用RxJava这样的流式框架。
有时候面试官会使用追问的形式来挖掘候选人的技术深度,所以大家在准备面试时要多问自己几个问什么,知其然并知其所以然。
当然,我也不赞同这种刨根问底式的拷问方式,尤其是揪着一些没有实用价值的细枝末节不放。所以本文也是提醒广大面试官,挖掘深度的同时要注意分寸,不能以将候选人难倒为目标来问问题。
好了,今天的文章就到这里,感谢阅读,喜欢的话不要忘了三连。大家的支持和认可,是我分享的最大动力。
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net