Jetpack-自定义LiveData解决数据粘性(倒灌),拿来就能用

背景

我们在使用LiveData的时候,有时需要在两个页面监听同一个LiveData数据的变化,从第一个页面进入第二个页面时,第二个页面刚刚订阅,就会收到第一个页面最后一次数据的变化。一般地,在使用LiveData时,我们都会先订阅,后setValue,但如果我们先setValue,后订阅,会发现我们之前setValue的值也会回调onChange,这就是数据粘性。造成这种数据粘性的原因在我上一篇文章有分析,这里就不再赘述,我们大多数情况是不需要这种粘性数据的,下面我们就通过自定义LiveData来去除粘性。

自定义LiveData和用法,拿来就能用

XMutableLiveData

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

第五步,我们最终目的是为了让mLastVersionmVersion相等,这样才不会执行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)

我们可以看出,去除粘性的关键就是在正式开始订阅的时候,保证mLastVersionmVersion版本一致,从而避免执行onChange回调,实现方式都是通过反射。Jetpack版本迭代很快,我们要掌握的是解决问题的思路,即使以后源码有改动,也可以按照这样的思路一步一步来解决~

你可能感兴趣的:(Jetpack学习笔记,android,kotlin,jetpack)