相信很多人在职业生涯的面试过程中都被问过一个问题?
面试官:我看你简历上有写 LiveData
,那你能说说 LiveData
是粘性的吗?
这确实是一个值得深入思考的知识点,今天就让我们站在Google设计者的角度来深入学习一下LiveData
。
关于这个问题,我们首先应该知道,粘性是什么意思?
不知道你对EventBus
熟不熟悉,我第一次接触粘性这个概念,就来自于EventBus
的粘性事件。
粘性事件:相对比普通事件,粘性事件支持先发送事件,再去注册订阅者。一旦完成订阅动作,这个订阅者就会接收到该粘性事件。
所以粘性其实就可以理解为观察者模式的升级,让观察者与被观察者对象之间更加的粘合。正常情况下,我们需要先注册观察者对象,然后再去更改被观察者对象,这样观察者对象才能接收到这个观察事件。而粘性,则支持先去触发更改被观察者对象,产生观察事件,然后再去注册观察者对象,注册成功后,该观察者对象还是可以接收到该观察事件,执行对应的观察动作。
本文对你有所帮助的话可以点赞支持一下喔, 需要更多android进阶开发资料可以留言获取~ 来个例子吧!看看是不是粘性的我们利用LiveData
来做APP的全局状态管理。
object GlobalState {
val jcTestNumberLd: MutableLiveData = MutableLiveData()
}
然后在Fragment
以及Activity
中观察该jcTestNumberLd
。
/** 观察 GlobalState 的 Activity */
class JcTestActivity : BaseVmDbActivity() {
override fun initView() {
viewBinding.incButton.setOnClickListener {
GlobalState.jcTestNumberLd.value =
if (GlobalState.jcTestNumberLd.value == null) {
1
} else {
GlobalState.jcTestNumberLd.value!!.toInt().inc()
}
}
}
override fun initObserve() {
GlobalState.jcTestNumberLd.observe(this, {
Log.e(TAG, "initObserve: jctestNumber = $it")
})
}
........
}
/** 观察 GlobalState 的 Fragment */
class EventFragment : BaseVmDbFragment() {
override fun setObservers() {
GlobalState.jcTestNumberLd.observe(viewLifecycleOwner, {
Log.e(TAG, "setObservers: jctestNumber = $it", )
})
}
........
}
注意:这里例子中的 EventFragment
并不是关联到 JcTestActivity
的。用户会先进入到 JcTestActivity
,然后由用户控制进入到另一个Activity中,加载EventFragment
。
我们来执行一下以下五步操作,来看一下输出的日志。
- 当我们第一次进入
JcTestActivity
时,注册了观察者,没有接收到观察事件,所以也就不会执行观察动作。 - 然后我们点击自增按钮为
jcTestNumberLd
赋予新值,接收到观察事件,执行观察动作,输出 1
。 - 再次点击自增按钮,有观察事件,执行观察动作,
输出 2
。 - 再次点击自增按钮,有观察事件,执行观察动作,
输出 3
。 - 然后我们到
EventFragment
中,注册新的观察者,发现直接接收到观察事件,执行观察动作,输出 3
。
输出结果:
E/JcTestActivity: initObserve: jctestNumber = 1
E/JcTestActivity: initObserve: jctestNumber = 2
E/JcTestActivity: initObserve: jctestNumber = 3
E/EventFragment: setObservers: jctestNumber = 3
复制代码
这就是粘性事件!所以说,LiveData是粘性的。
LiveData 是怎么实现粘性的呢?在知道LiveData
是粘性后,我不经问自己:它是怎么实现粘性的呢?
这里我们先来回顾一下EventBus
粘性事件的实现原理。
EventBus
在发送粘性事件时,会将这粘性事件存到一个叫做stickyEvents
的集合中,然后等注册订阅新的观察者对象时,会去遍历该集合中的粘性事件,如果有找到对应的粘性事件,就将该粘性事件发送给该观察者。
那LiveData
是不是也是以同样的原理来实现粘性的呢?
public LiveData(T value) {
mData = value;
mVersion = START_VERSION + 1;
}
/**
* Creates a LiveData with no value assigned to it.
*/
public LiveData() {
mData = NOT_SET;
mVersion = START_VERSION;
}
从 LiveData
的构造函数中可以发现有一个 mVersion
参数,它代表着 LiveData
的版本号,每当我们进行setValue
时,都会让mVersion
进行自增。
另外,ObserverWrapper
这个观察者包装类中也有一个int mLastVersion = START_VERSION
版本号。
这两个版本号分别是被观察者对象与观察者对象的版本号,那这二者之间又有什么关系呢?
在判断是否通知观察者的 considerNotify(ObserverWrapper observer)
方法中,会对这两个版本号进行比较。
private void considerNotify(ObserverWrapper observer) {
...省略代码...
//如果观者者的版本号 >= LiveData的版本号,就说明该观察者已经接收过该观察事件,也就不再分发。
if (observer.mLastVersion >= mVersion) {
return;
}
//反之,分发观察事件给该观察者,让其执行对应的观察动作,并更新观察者的版本号
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
概括一下:根据比对观察者对象的版本号与LiveData
的版本号来判断是否分发当前版本的数据给该观察者。如果观察者对象的版本号大于等于LiveData
的版本号,也就说明该观察者已经接收过当前版本的数据了,也就不需要再次分发了(等待下一次数据更新)。反之,则分发当前版本的数据给该观察者,让其执行对应的观察动作,并更新观察者的版本号,也就是更新为LiveData
的版本号。
我们利用 Hook 分别拿到 LiveData
的 mVersion
以及 ObserverWrapper
的 mLastVersion
来看一下。
/** 主动 hook 检测版本号 */
dataBinding.hookCheckVersionButton.setOnClickListener {
GlobalState.jcTestNumberLd.hook()
}
fun LiveData.hook() {
//get liveData mVersion
val mVersion = this.javaClass.superclass.getDeclaredField("mVersion")
mVersion.isAccessible = true
val mVersionValue = mVersion.get(this)
Log.e(TAG, "hook: LiveData mVersion = $mVersionValue")
val mObservers = this.javaClass.superclass.getDeclaredField("mObservers")
mObservers.isAccessible = true
//SafeIterableMap
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?