我们在使用LiveData的时候,有时需要在两个页面监听同一个LiveData数据的变化,从第一个页面进入第二个页面时,第二个页面刚刚订阅,就会收到第一个页面最后一次数据的变化。一般地,在使用LiveData时,我们都会先订阅,后setValue
,但如果我们先setValue
,后订阅,会发现我们之前setValue
的值也会回调onChange
,这就是数据粘性。造成这种数据粘性的原因在我上一篇文章有分析,这里就不再赘述,我们大多数情况是不需要这种粘性数据的,下面我们就通过自定义LiveData来去除粘性。
class XMutableLiveData<T> : MutableLiveData<T>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
// 1.得到mLastVersion
val liveDataClass = LiveData::class.java
val mObserversField: Field = liveDataClass.getDeclaredField("mObservers")
mObserversField.isAccessible = true
val mObserversObject: Any = mObserversField.get(this)
val mObserversClass: Class<*> = mObserversObject.javaClass
val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java)
get.isAccessible = true
val invokeEntry: Any = get.invoke(mObserversObject, observer)
var observerWrapper: Any? = null
if (invokeEntry != null && invokeEntry is Map.Entry<*, *>) {
observerWrapper = invokeEntry.value
}
if (observerWrapper == null) {
throw NullPointerException("observerWrapper is null")
}
val supperClass: Class<*> = observerWrapper.javaClass.superclass
val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion")
mLastVersion.isAccessible = true
// 2.得到mVersion
val mVersion: Field = liveDataClass.getDeclaredField("mVersion")
mVersion.isAccessible = true
// 3.mLastVersion=mVersion
val mVersionValue: Any = mVersion.get(this)
mLastVersion.set(observerWrapper, mVersionValue)
}
}
object TestLiveData {
//使用原生的LiveData
// val mData: MutableLiveData by lazy { MutableLiveData() }
//使用自定义的LiveData
val mData: XMutableLiveData<String> by lazy { XMutableLiveData() }
}
class LiveDataActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_data)
TestLiveData.mData.value = "粘性数据"
TestLiveData.mData.observe(this, {
Toast.makeText(this@LiveDataActivity, it, Toast.LENGTH_SHORT).show()
})
thread {
Thread.sleep(3000)
TestLiveData.mData.postValue("正常数据")
}
}
}
运行后可以看出,当我们在TestLiveData中使用原生MutableLiveData时,进入页面首先会打印粘性数据,此时我们还没有订阅,3000ms以后再打印正常数据。当我们使用自定义的XMutableLiveData时,只打印了正常数据,达到了预期效果。
造成数据粘性的原因在上一篇文章有分析,在订阅之前执行setValue
时,mVersion
已经++,所以在执行observe
方法时,不满足observer.mLastVersion >= mVersion
,于是会回调onChange
,把之前setValue
时保存的mData
带过去。如果想解决这个问题,只需要在observe
时,把两个版本设置为一致即可,所以我们需要通过反射,来修改mLastVersion
值。注意:我们只需要在observe
时修改即可,所以我们的自定义LiveData只需要重写observe
方法就可以了。
要拿到mLastVersion
,要先拿到ObserverWrapper
,所有的ObserverWrapper
存储在了mObservers
中,mObservers
又是LiveData的成员变量。所以捋清了这些,就可以反过来一步一步通过反射来拿了。
第一步,我们先通过反射来拿mObservers
val liveDataClass = LiveData::class.java
val mObserversField: Field = liveDataClass.getDeclaredField("mObservers")
mObserversField.isAccessible = true
第二步,mObservers
是个Map,里面肯定有get
方法,我们要拿Map中的value
,就要先执行它的get
方法,所以我们反射来拿get
方法
val mObserversObject: Any = mObserversField.get(this)
// 得到map对象的class对象
val mObserversClass: Class<*> = mObserversObject.javaClass
// 获取到mObservers对象的get方法 protected Entry get(K k) {
val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java)
get.isAccessible = true
第三步,执行get
方法,取到value
,也就是observerWrapper
val invokeEntry: Any = get.invoke(mObserversObject, observer)
var observerWrapper: Any? = null
if (invokeEntry != null && invokeEntry is Map.Entry<*, *>) {
observerWrapper = invokeEntry.value
}
第四步,从ObserverWrapper中拿到mLastVersion
val supperClass: Class<*> = observerWrapper.javaClass.superclass
val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion")
mLastVersion.isAccessible = true
第五步,我们最终目的是为了让mLastVersion
与mVersion
相等,这样才不会执行onChange
回调,那么mVersion
值是多少呢,我们去LiveData类中去拿一下mVersion
val mVersion: Field = liveDataClass.getDeclaredField("mVersion")
mVersion.isAccessible = true
最后一步,把mLastVersion
设置为mVersion
一致即可
val mVersionValue: Any = mVersion.get(this)
mLastVersion.set(observerWrapper, mVersionValue)
我们可以看出,去除粘性的关键就是在正式开始订阅的时候,保证mLastVersion
和mVersion
版本一致,从而避免执行onChange
回调,实现方式都是通过反射。Jetpack版本迭代很快,我们要掌握的是解决问题的思路,即使以后源码有改动,也可以按照这样的思路一步一步来解决~