一、背景
我想在界面弹出toast,这个toast的内容我保存在ViewModel中的一个LiveData变量中:
val toastMsg = MutableLiveData<String>()
然后在网络加载失败时给它赋值:
fun getUpdateInfo() {
launchOnUI {
_updateData.value = NetworkRequestUIState(isLoading = true)
val result = mineRepository.getUpdateInfo("LowCarbon")
if (result is ResponseResult.Error) {
_updateData.value =
NetworkRequestUIState(isLoading = false, isError = result.errorMsg)
//给toastMsg赋值
toastMsg.value = "获取更新失败"
} else if (result is ResponseResult.Success) {
_updateData.value =
NetworkRequestUIState(isLoading = false, isSuccess = result.data)
}
}
}
接着在View层进行observe,代码如下:
//toast信息
toastMsg.observe(this@MineActivity){
if (it.isNotEmpty()){
Toast.makeText(this@MineActivity, it, Toast.LENGTH_SHORT).show()
}
}
这里当竖屏时,网络不好时,会弹出toast;然后进行横屏后,会发现又弹了一次toast,这说明 "获取更新失败" 这个事件又被消费了一次,这明显不符合逻辑,那为什么会又弹出一次呢。
二、原因
这个原因其实很简单,当配置发生变化时:
-
Activity会重新走一遍生命周期函数,而这时会再次添加观察者,调用observer方法,方法内会new一个新的ObserverWrapper对象,就会有一个新的 mLastVersion = -1。
-
当Activity重新活跃,会再次执行事件分发。
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { //当Activity重建时,生命周期会重新走一遍 Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { removeObserver(mObserver); return; } Lifecycle.State prevState = null; //上一次的state跟当前的不同时,执行事件分发 while (prevState != currentState) { prevState = currentState; //当走到ON_RESUME时会触发页面活跃,会notify数据 activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); } } } -
在事件分发时,会判断 mLastVersion >= mVersion,就分发失败;反之,如果分发事件成功,就将当前LiveData的
mVersion赋值给mLastVersion。很明显这里mLastVersion是一个新初始化的对象,初始值为-1,这里就是数据倒灌的原因。// 当前宿主Activity的mLastVersion大于等于LiveData的mVersion,不分发 // 这里就是数据倒灌的原因 if (observer.mLastVersion >= mVersion) { return; }
三、解决方案
再来回顾下,数据倒灌的常见场景:
- 屏幕旋转
- 用户手动切换系统语言
解决方案:
- 如果应用不需要横屏,就设置为永久竖屏。
- 官方扩展的SingleLiveEvent。
- 美团反射修改mVersion。
- UnPeek-LiveData。