LiveData数据通知解析以及死锁问题处理
前言
LiveData
是Android
常用的组件,它代表具有生命周期的数据,是MVVM
框架组成不可或缺的一部分
问题场景
在我们的代码案例中,我们点击A页面
的List
中的一个Item
,然后将数据传递并跳转到B Fragment
,然后在B Fragment
中使用LiveData
对数据进行业务处理从而展示,但是由于编码问题导致当点击某一条Item
出错时,点击后续的Item
都无法正常显示B Fragment
的UI
造成白屏的现象,由于这个问题我对LIveData
的数据通知方式的源码做了分析
源码分析
当我们对LivaData
进行赋值并且它处于活动状态时会调用
@MainThread
protected void setValue(T value) {
//断言此函数是在主线程执行,否则会抛出异常
assertMainThread("setValue");
//将此LiveData的数据版本加一
mVersion++;
//LiveData中真实存储内容的字段,getValue返回的就是此字段的值
mData = value;
//进行数据通知处理
dispatchingValue(null);
}
dispatchingValue
函数中有两个重要的变量,mDispatchingValue
和mDispatchInvalidated
,并且这两个字段只有在这个函数中有使用,之所以不做成局部变量是因为这两个字段控制的是所有此LiveData
数据分发的行为,而不仅是本次通知的行为
mDispatchingValue
表示数据是否正在调度中,如果上一次设置的数据正在分发中则会return
掉本次数据通知操作
mDispatchInvalidated
表示数据调度是否失效,如果在本次设置数据调度的过程中又收到新的调度,则本次调度就会失效
void dispatchingValue(@Nullable ObserverWrapper initiator) {
//是否正在进行数据调度
if (mDispatchingValue) {
//如果正在进行数据调度则上次正在执行的数据调度标记已失效
mDispatchInvalidated = true;
//如果正在进行数据调度则本次数据调度直接return取消
return;
}
//将是否正在进行数据调度的值设置为true,表示数据正在调度中
mDispatchingValue = true;
//下面这个循环就是数据调度的过程,它会遍历所有注册的观察者,对他们通知新的数据
do {
//在数据调度开始时将是否失效字段置为false
mDispatchInvalidated = false;
//当调用setValue时initiator为null
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;
}
}
}
//外层循环的执行条件是数据失效,也就是说如果在通知数据的过程中如果数据失效则会重新执行一次do代码块,从而通知最新的数据
} while (mDispatchInvalidated);
//表示数据调度结束,开关打开,可以接受下一次的数据设置
mDispatchingValue = false;
}
从dispatchingValue
函数值你给我们可以看到
1.mDispatchingValue
字段用于表示数据通知是否正在执行中
2.如果在执行中又来了一条数据虽然由于mDispatchingValue 的缘故retun了本次执行,但是由于失效字段mDispatchInvalidated
的控制do
代码块会多次执行,从而只会通知最新的数据,这也就是为什么我们连续设置Livedata
的值只有最后一次会生效的原因
considerNotify
函数表示目标观察者考虑是否要通知数据更新
private void considerNotify(ObserverWrapper observer) {
//如果观察者的生命周期不可用则放弃此次通知,生命周期依赖于Activity或者Fragment的生命周期
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
//再次检查是否为非活动状态,并且额外的通知一次状态变更
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//如果观察者的数据版本已经高于此次数据版本,则取消
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
//调用onChanged函数,通知此次数据变更
observer.mObserver.onChanged((T) mData);
}
3.在函数dispatchingValue
中如果数据过期,会在调用considerNotify
之后才调用了break
,这并不会造成通知旧数据的漏洞,因为在considerNotify
函数中直接通知的最新的mVersion
以及mData
问题解决
我们可以看到considerNotify
函数的最终目的是要去调用onChanged
函数,也就是我们监听LiveData
的代码块,我们会发现如果onChanged
代码块中有异常抛出则函数dispatchingValue
会直接从considerNotify(iterator.next().getValue())
处直接中断,造成mDispatchingValue
的值无法变回false
状态,以致于此LiveData
再也无法正常的进行数据通知的操作一直停留在了工作中的状态,造成了一种类似死锁的现象
4.为了防止dispatchingValue
造成的死锁,请关注你的onChanged
代码块防止有异常出现破坏数据通知的逻辑
这也就印证了为什么在我们的业务中当一条Item
出错时,再点击其他的Item
无法正常收到LiveData
更新的现象,所以解决的方案就是在onChanged
函数,也就是我们监听LiveData
的代码块不要有异常抛出进行异常修复或者特殊场景进行try catch
,要么就使用不会抛出异常的简单业务
总结
1.mDispatchingValue
字段用于表示数据通知是否正在执行中,防止数据通知操作重复
2.如果在执行中又来了一条数据虽然由于mDispatchingValue 的缘故retun了本次执行,但是由于失效字段mDispatchInvalidated
的控制do
代码块会多次执行,从而只会通知最新的数据,这也就是为什么我们连续设置Livedata
的值只有最后一次会生效的原因
3.在函数dispatchingValue
中如果数据过期,会在调用considerNotify
之后才调用了break
,这并不会造成通知旧数据的漏洞,因为在considerNotify
函数中直接通知的最新的mVersion
以及mData
4.为了防止dispatchingValue
造成的死锁,请关注你的onChanged
代码块防止有异常出现破坏数据通知的逻辑
三个函数不是很复杂的系统函数实现了数据的防重复通知,连续多次通知只取最后一次的性能优化,在数据非活动状态时不更新三个强大的LiveData
的基础功能,这是我们在开发中设计代码结构的一个很好的学习案例,比如可以应用到用户连续需点击多次提交只取最后一次的业务需求中
欢迎关注Mike的简书
Android知识整理