文章目录
在使用LiveData的时候,会有疑问,LiveData 是怎么做到先发送再注册,依然能够接收到事件的。还有就是会碰到切换屏幕,导致重复的操作,也就是所谓的数据倒灌。这篇文章将一一揭晓。
我们写一个CounterViewModel 继承 ViewModel,然后声明一个_counter变量。
通过点击事件触发incrementCounter() 给_counter.value 赋值,每次都加一。
这里就是通过LiveData 的 setValue 发送。
class CounterViewModel : ViewModel() {
private val _counter = MutableLiveData<Int>()
val counter: LiveData<Int>
get() = _counter
init {
_counter.value = 0
}
fun incrementCounter() {
val count = _counter.value ?: 0
_counter.value = count + 1
}
}
接着我们在Activity中的进行注册监听,我们通过ViewModelProvider 获取 viewModel 对象,
然后通过observe 进行注册监听事件。实际是通过onChanged
进行监听setValue传过来的值。
viewModel.counter.observe(this, object: Observer {
override fun onChanged(t: Int?) {
Log.d(“ssz”, “值:$t”)
}
})
下面是lambda 简化写法,实际还是通过onChanged 进行数据的接收的
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_test_mvvm)
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.counter.observe(this, {
t -> Log.d("ssz", "值:$t")
}
)
Log.d("ssz", "onCreate")
}
总的来说,LiveData 通过setValue 发送,然后通过 Observer 里面的onChanged 进行接收。
通过上面的例子,我们确实能接收到数据了。那么什么是粘性事件呢,就是我们先发送再注册,这个时候依然能接收到消息,那么这就是粘性事件。就好比是我们熟悉的EventBus中的粘性事件。
那么LiveData 真的有粘性事件吗,我们打个日志就知道了,还是上面的例子,只是加上这个日志。
init {
_counter.value = 0
Log.d("ssz", "初始化counter值")
}
这是上面代码执行后的日志:
可看到我们是先执行了_counter.value 的初始化,也就是数据的发送,然后才接着执行oncreate中observe中的监听的。说明了LiveData 具有粘性事件。
然后这个时候,你如果正常点击,那么这个时候,就是先注册再发送了。这个就是回到正常先注册再发送了。
那么LiveData 是怎么做到粘性事件的呢。我们看下源码:
下面这是数据发送的源码:
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
这是注册监听observe 的关键源码:
@SuppressWarnings("WeakerAccess") /* synthetic access */
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;
}
dispatchingValue 会调用 下面的 considerNotify,也是粘性事件的真正原因
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) { //关键1
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData); //关键2
}
mLastVersion 的默认初始值是-1,mVersion 的默认初始值也是-1,当我们先执行发送的时候,进行了自增,mVersion 就变成了0,当我们执行observe 进行监听的时候,observer.mLastVersion >= mVersion 这个条件就不成立了,因为此时mLastVersion 是-1,小于 mVersion 了。因为这时 mVersion 是0。
那么就不会return,就会继续执行接下来的代码
observer.mObserver.onChanged((T) mData);
看到了没有,这个onChanged 就是我们observe 里头的 onChanged。这就把数据传给了监听了。
这就是为什么,我们先发送也能接收到数据的原因了,也就是LiveData 所谓的粘性事件了。
这个时候你可能有个疑问,那正常是怎么样的呢?
正常先注册,后发送。
这里是注册的关键源码:
@SuppressWarnings("WeakerAccess") /* synthetic access */
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()); //关键2
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
这是发送源码:
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null); //关键1
}
这里我们发现setValue 会去调用上面的dispatchingValue 就是会去调用注册的onChanged。一步步看。
我们能看到关键2,这里就是执行我们上面分析的considerNotify了。因为我们是先注册,再发送的,注册的 observer.mLastVersion 还是默认值-1,而发送还是老样子 mVersion 会自增,变成0。和上面分析的一样,observer.mLastVersion 小于 mVersion。这样就会执行后面的observer.mObserver.onChanged((T) mData),也就是我们注册时候的onChanged,我们就能在onChanged 接收数据了。
小结:上面我们分析了先注册后发送,还有先发送后注册的两种情况。
上面能通过LiveData进行数据的发送和接收,但是当你进行横竖屏的切换,你会发现日志跟着打印的,你并没有点击发送数据呀,为什么会这样呢,这是因为当你横竖屏切换的时候,Activity进行了重新的创建,onCreate重新走了一遍。
你可能想,我这里还是走得LiveData的注册啊,但是这个时候,源码里头重新创建了个监听事件,重新new 了一个新的观察者,(相当于是上面我们提到的先发送,后注册)也就是粘性事件起了作用。
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); //这里是关键
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
new LifecycleBoundObserver 重新new了一个观察者,这个LifecycleBoundObserver 其实继承了ObserverWrapper 也就是我们的mLastVersion 初始化的类。
因为重新创建了新的观察者,也就是observer.mLastVersion 重新变为初始值-1,而mVersion因为前面的发送数据,可能很多次发送了,已经大于等于0了。所以,这个时候一定会执行observer.mObserver.onChanged((T) mData)。就我们上面分析的粘性事件。这个时候,你就会收到最后一次发送的数据。
那么怎么解决呢?
1、保证Activity进行了不重新创建,也就是oncreate 不重复执行。
2、通过使用SingleLiveEvent。
第一种
很简单,直接在清单中我们配置下,比如android:configChanges=“orientation|screenSize” 保证旋转屏幕不会重新创建Activity。当然还有系统文字变化,根据自己的需要进行配置。目的保证不会重新创建Activity就行了。
第二种,
我们这里重点讲下SingleLiveEvent:
open class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, {
if (pending.compareAndSet(true, false)) {//这里是关键
observer.onChanged(it)
}
})
}
@MainThread
override fun setValue(value: T?) {
pending.set(true)
super.setValue(value)
}
@MainThread
fun call() {
value = null
}
}
使用方法:
private val _snackbarText = SingleLiveEvent<String>()
val snackbarText: LiveData<String> = _snackbarText
fun onSnackbarClick() {
_snackbarText.value = "Snackbar Clicked"
}
viewModel.snackbarText.observe(this, { text ->
// 显示 Snackbar
val rootView: View = findViewById(android.R.id.content)
Snackbar.make(rootView, text, Snackbar.LENGTH_SHORT).show()
Log.d("ssz","点击了" + text)
})
我们新建一个SingleLiveEvent 继承MutableLiveData,然后通过重写observe,在里头进行判断,一旦执行过就不再执行,除非下一次的发送。这样就保证,不会数据倒灌的发生。
源码地址:https://github.com/shenshizhong/LiveDataDemo
1 、简单介绍LiveData 的使用
2 、分析了粘性事件是怎么产生的,
3、 分别对先注册后发送,和先发送后注册进行了分析。
3 、分析了数据倒灌怎么回事,以及怎么处理
如果对你有一点点帮助,那是值得高兴的事情。:)
我的csdn:http://blog.csdn.net/shenshizhong
我的简书:http://www.jianshu.com/u/345daf0211ad