livedata是jetpack中一个比较好用的库,使用观察者模式实现了数据订阅或者更改,通知观察者的功能。
正常的使用方式为,先增加订阅,而后使用setValue或者postValue更新数据。代码如下:
//泛型为数据类型
var live = MutableLiveData<String>()
//在activity或者fragment中使用
live.observe(this,object :Observer<String>{
override fun onChanged(t: String?) {
}
})
当调用live.value="123"或者live.postValue="123",会回调onChange()方法,从而达到通知的效果。
粘性数据
有一个场景,有两个Activity,分别为A,B,在A中订阅liveData,设置一个textview,点击textview时候,更改livedata的value,同时打开Activity-B,在B中的onCreate订阅这个LiveData,代码如下:
class TestActivity : BaseBindAc<LayoutPublicBinding>() {
override fun getLayoutId(): Int = com.poolofthree.common.R.layout.layout_public
override fun initView() {
mBindView.text.setOnClickListener {
SingleModel.liveData.value = "release-value--1111"
SingleModel.liveData.value = "release-value--2222"
startActivity(Intent(this@TestActivity, Test2Activity::class.java))
}
SingleModel.liveData.observe(this) {
mBindView.text.text = it
}
}
}
class Test2Activity : BaseBindAc<LayoutPublicBinding>() {
override fun getLayoutId(): Int = com.poolofthree.common.R.layout.layout_public
override fun initView() {
SingleModel.liveData.observe(this) {
Log.e("123","Test222222Activity--------------${it}")
mBindView.text.text = it
}
}
}
这种情况下打开Test2Activity,text上显示的为release-value--2222。那这就有问题了,我希望打开Test2Activity之后Test2Activity再开始订阅后续的事件,之前的事件我不需要接收到。
这就是所谓的“粘性数据”。大多数情况下我们是不需要这个粘性事件的。那这算是一个bug吗?其实是不算的,LiveData设计的初衷是在Activity状态变化的情况下能很好的保存数据以及状态,比如Activity横竖屏切换后恢复界面显示的数据,所以这算是谷歌故意为之的。只是我们开发者的用法比较魔幻,违背了谷歌设计的初衷,但是我们需要解决自己的问题。
解决之前需要先研究一下,这个问题是怎么发生的。
先从订阅开始看起,也就是 observe,源码如下:
mObservers.putIfAbsent(observer, wrapper)是一个Map,观察者为key,包装类为value。
owner.getLifecycle().addObserver(wrapper)为实际订阅的代码。
设置数据是使用的setValue或者postValue,两者大同小异,我们只看setValue,源码如下:
可以看到,内部维护了一个Version,初始值为-1,每setValue一次,这个version加1,然后开始分发数据:
第一次进来,initator为空,走的是else,先遍历mBoservers这个map,然后拿出观察者对象,也就是在Activity中传入订阅函数的回调接口的包装对象LifecycleBoundObserver,调用considerNotify,遍历是因为同一个livedata可能有多个订阅的回调。
considerNotify代码如下:
shouldBeActive这里有一个递归或者说是循环,这个判断是考虑到 activity在后台突然回到前台时,状态变更,这样可以从循环等待马上响应更新数据。
if (observer.mLastVersion >= mVersion) 如果观察者的version< 被观察者的version,就会同步两者的version,而后调用观察者的onChanged,也就是观察者自己的实现逻辑。
那从上面不难看出,粘性事件就是因为最后一步的同步代码,同步之后调用onChange导致的。所以若想解决粘性事件,就需要用反射来修改最后一步,也就是这句:
observer.mObserver.onChanged((T) mData);
另外还有一个需要注意的点,在ComponentActivity中有一个LifecycleRegistry,该对象会在生命周期事件前进或者回退的时候,调用handleLifecycleEvent,而后调用自己的moveToState,sync方法,sync方法中有前进或者回退对应的方法,这两个方法中最终会遍历观察者对象包装类对象的Map,并调用dispatchEvent方法,而该方法会间接调用包装类中的实际观察者的onStateChanged(),也就对应了LiveData中addObserver的LifecycleBoundObserver对象,这样在Activity生命周期变化时,livedata才能感知到。