Android面試:說一下 LiveData 的 postValue ?與SetValue有什麼區別?連續呼叫會有什麼問題?為什麼?
眾所周知,程式設計師面試的時候,很多面試官喜歡會就一個問題不斷深入追問。
例如一個小小的 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