前言:測試大佬突然發(fā)現(xiàn)頁面上有一塊UI在某些情況下無內(nèi)容展示,但是接口有下發(fā)對應(yīng)字段。這種偶現(xiàn)的bug就很頭禿了,排查一波也沒發(fā)現(xiàn)啥異常,就是個簡單的接口請求,然后在LiveData回調(diào)中更新UI。這樣看來問題出在LiveData了?正好本地也復(fù)現(xiàn)了這個問題,瘋狂刷新調(diào)用接口,UI也沒發(fā)生變化。
趕緊debug一下,發(fā)現(xiàn)LiveData.setValue()此時卡在了dispatchingValue()方法開頭處,mDispatchingValue為true,直接return,沒有走后續(xù)的observer.onChange()回調(diào)。mDispatchingValue字段顧名思義,標記正在分發(fā)事件,此時繼續(xù)setValue()不會立即更新。mDispatchingValue是個全局變量,找一下引用發(fā)現(xiàn)賦值處全在dispatchingValue()方法中。
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
所以說正常流程下mDispatchingValue初始值應(yīng)該為false,然后在do的入口處置為true,標記正在分發(fā)事件,分發(fā)事件后跳出循環(huán)再次置為fasle。有可能出現(xiàn)問題的地方就在do中的分發(fā)事件,considerNotify()就是我們的回調(diào),回調(diào)中如果出現(xiàn)異常,執(zhí)行邏輯跳到外部catch塊,mDispatchingValue就沒法在最后重置為false,那么后續(xù)的setValue()都會被return掉。
這里有的同學(xué)就會問了,LiveData回調(diào)中出現(xiàn)異常不就崩潰了嗎,崩潰了就能找到問題解決了啊,還哪有后續(xù)不分發(fā)事件的坑呢。but基礎(chǔ)框架大概率會把接口調(diào)用整個流程try catch住,我這邊用的協(xié)程,整個協(xié)程體都被try catch了所以不會崩潰。我這邊的case可以等同于把setValue()方法try catch住,然后應(yīng)該是某個接口字段的問題拋出異常,進而導(dǎo)致后續(xù)事件不再分發(fā)。
思考了一下,找到接口字段fix掉貌似是個不錯的選擇,但是吧,我們能相信后端嗎?就算和后端大佬遵守了彼此的約定,那我們能保證回調(diào)中永遠不出現(xiàn)異常嗎?回調(diào)中的業(yè)務(wù)代碼崩潰了其實也還好,起碼能快速定位然后解決,就怕被外部catch住了,然后一臉懵逼不走回調(diào)。
先來個兜底方案吧,在你確定回調(diào)被catch住不會崩潰進而引發(fā)這個bug的時候,不妨自己catch處理。
fun <T> MutableLiveData<T>.safeObserve(owner: LifecycleOwner, onChange: (T) -> Unit) {
this.observe(owner) {
try {
onChange(it)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
思路很簡單,套一層try catch,即使回調(diào)中產(chǎn)生異常,dispatchingValue()方法也會走完。
當然啦,該改的還是要改,具體是哪個字段沒下發(fā)有可能為空還是要保護一下的。我應(yīng)該是被data class坑了,解析json反射給字段賦值不會走構(gòu)造函數(shù)的默認值,最好都寫成可空類型。
如果你不希望發(fā)生崩潰,也不希望LiveData后續(xù)永遠不走回調(diào),可以嘗試上面這個方案。搬磚式的把observe都改為safeObserve也很難受啊,ASM替換一下調(diào)用比較舒服,這個沒什么難度也就不展開了。ASM統(tǒng)一替換的話不要太暴力,搞個注解啥的做成可配才是王道。