溯源
ViewModel
将数据保留在内存中,这意味着开销要低于从磁盘或网络检索数据。ViewModel 与一个 Activity(或其他某个生命周期所有者)相关联,在配置更改期间保留在内存中,系统会自动将 ViewModel 与发生配置更改后产生的新 Activity 实例相关联。
原理描述
LiveData是一个观察者模式,被观察者只要改变了观察者会收到通知。在页面重建时,LiveData自动推送最后一次数据供我们使用,造成数据的再次倾泻。也就是说 Activity异常销毁然后重建,ViewModel
会保存销毁之前的数据,然后在Activity重建完成后进行数据恢复,所以LiveData成员变量中的mVersion
会恢复到重建之前的值。
但是Activity重建后会调用LiveData的observe()
方法,方法内部会重新new
一个实例,会将mLastVersion
恢复到初始值
。
场景描述
1. 在日常开发中,采用ViewModel + LiveData 架构形式,在二级页面,要接收一级页面的数据驱动。
2. 当多屏生命周期切换、或者屏幕旋转等场景,会导致ViewModel的LiveData再次将内存存留的lastest数据重复发送,引起界面的重复跳转或者界面的重复绘制,或者造成数据的失真。
3. ViewModel 与一个 Activity(或其他某个生命周期所有者)相关联,在配置更改期间保留在内存中,系统会自动将 ViewModel 与发生配置更改后产生的新 Activity 实例相关联。形成粘性数据的脏数据业务场景。
- 屏幕旋转
- 用户手动切换系统语言
如何堵漏
事件分发的过程先判断mVersion
和mLastVersion
,当mLastVersion < mVersion
时会onChanged((T) mData);进行分发。每次设置setValue时mVersion++,然后赋值给mLastVersion。
- 如果应用不需要横屏,就设置为永久竖屏。
- 如果当前Activity回到前台LiveData不需要接收最新的数据,可以使用下面三中扩展的LiveData
- 设置android:configChanges="orientation|screenSize",这样普通生命周期就不走了
LiveData数据分发的条件
LiveData
private void considerNotify(ObserverWrapper observer) {
// 如果当前的宿主Activity不是Active状态,不分发
if (!observer.mActive) {
return;
}
// 宿主Activity的如果在后台,不分发
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
// 当前宿主Activity的mLastVersion大于等于LiveData的mVersion,不分发
// 这里就是数据倒灌的原因
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
// 分发事件
observer.mObserver.onChanged((T) mData);
}
版本号
,在Activity销毁重建的时候会重新订阅,如果版本号大于等于mVersion
就会再次分发通知,导致activity
会 再次
收到通知。
解决方案
1. 可以 反射 修改mVersion,这个不太建议使用。
private abstract class ObserverWrapper {
final Observer<? super T> mObserver;
boolean mActive;
// 第一处
int mLastVersion = START_VERSION;
}
private void considerNotify(ObserverWrapper observer) {
...
// 第二处
if (observer.mLastVersion >= mVersion) {
return;
}
// 第三处
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
从上可知,屏幕旋转前,observer.mLastVersion == mVersion ==2。但是屏幕旋转后,mLastVersion的值却变成了-1。这里就是问题所在了。
2. 利用SingleLiveEvent 使 observe#LiveData
时只 一次 onChanged操作
SingleLiveEvent
利用 AtomicBoolean
(默认为false)进行赋值,当LiveData 进行 setValue时改变 AtomicBoolean 的值(set(true))
使用 AtomicBoolean.compareAndSet(true,false)
方法,先进行判断(此时的AtomicBoolean 的值为true)与 compareAndSet设置的except值(第一个参数)比较,因为相等所以将第二个参数设置为AtomicBoolean
值设为false函数并返回 true ),这里其实就是利用了AtomicBoolean
原子性,执行了一个比较和重新赋值的过程。
当再次进入该页面虽然 LiveData值并没有改变,仍然触发了 observer方法,由于 AtomicBoolean已经为 false ,但是 except值为 true,与if 进行判断所以 并不会继续触发 onChanged(T)方法,即只有在 setValue时相应一次onChanged(T)方法。
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w("SingleLiveEvent", "Multiple observers registered but only one will be notified of changes.")
}
super.observe(owner, { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
@MainThread
fun call() {
value = null
}
}
注意
系统内存不足,杀到应用后台,也会导致Activity重建,但是不会LiveData导致数据倒灌。