眾所周知,程序員面試的時候,很多面試官喜歡會就一個問題不斷深入追問。
例如一個小小的 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 <T> Observable<T>.toLiveData(): LiveData<T> = RxLiveData(this)
class RxLiveData<T>(
private val observable: Observable<T>
) : LiveData<T>() {
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