New ideas to solve LiveData data backflow

Data backflow phenomenon

I believe many people already know about the problem of LiveData's "data backflow", so I'll mention it here. The so-called "data backflow": It is actually similar to sticky broadcast. When a new observer starts to register for observation, the last historical data sent last time will be passed to the currently registered observer .

For example, in the following example code:

val testViewModel = ViewModelProvider(this)[TestViewModel::class.java]
testViewModel.updateData("第一次发送数据")
testViewModel.testLiveData.observe(this,object :Observer<String>{
    override fun onChanged(value: String) {
        println("==============$value")
    }
})

updateDataThe method sends data once, and when the LiveData observemethod is called below, it will be printed immediately ==============第一次发送数据. This is the "data backflow" phenomenon mentioned above.

cause

The reason is actually very simple. In fact, there is LiveDataan internal mVersionfield, record version, whose initial value mVersionis -1. When we call it setValueor postValueit mVersionwill +1; for each observer's encapsulation ObserverWrapper, its initial value mLastVersionis also -1, that is, For each newly registered observer, its mLastVersionis -1; when LiveDatasetting this ObserverWrapper, if LiveDatathe is mVersiongreater ObserverWrapperthan the current one will be forced to be mLastVersionpushed to .LiveDatavalueObserver

That is the following code

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }

        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        // 判断observer的版本是否大于LiveData的版本mVersion
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

So to solve this problem, there are two ways of thinking:

  • By changing ObserverWrapperthe value of each version number
  • In some way, ensure that the first distribution does not respond

Solution

Currently there are three solutions available on the Internet

Only respond once at a time

public class SingleLiveData<T> extends MutableLiveData<T> {
    private final AtomicBoolean mPending = new AtomicBoolean(false);

    public SingleLiveData() {
    }

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, (t) -> {
            if (this.mPending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }

        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        this.mPending.set(true);
        super.setValue(t);
    }

    @MainThread
    public void call() {
        this.setValue((Object)null);
    }
}

This method can solve the problem of sending historical data back, but Observeit does not work for multiple monitors. It can only be a single monitor. If there are multiple monitors, only one can receive it normally, and the others will not work properly.

reflection

This method is to obtain the version number of LiveData through reflection every time an observer is registered, and then modify the version number value of the current Observer through reflection. The advantages of this approach are:

  • ObserverAbility to monitor multiple
  • Solve the stickiness problem

But there are also disadvantages:

  • Every observertime you register, you need to reflect the updated version, which is time-consuming and has performance issues.

UnPeekLiveData

public class SingleLiveData<T> extends MutableLiveData<T> {
    private final AtomicBoolean mPending = new AtomicBoolean(false);

    public SingleLiveData() {
    }

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        super.observe(owner, (t) -> {
            if (this.mPending.compareAndSet(true, false)) {
                observer.onChanged(t);
            }

        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        this.mPending.set(true);
        super.setValue(t);
    }

    @MainThread
    public void call() {
        this.setValue((Object)null);
    }
}

This is actually SingleLiveDataan upgraded version of the above. SingleLiveDataIt uses one variable to control all Observers, and each Observerone used above uses a control identifier for control. Every setValuetime, all Observerswitches are turned on to indicate that distribution can be accepted. After distribution, turn off the currently executed Observerswitch, that is, it cannot be executed for the second time unless you restart setValue. This method is basically perfect for price comparison, except that there is an extra one inside to HashMapstore each Observerlogo. If Observerthere are more, there will be a certain amount of memory consumption.

new ideas

Let’s first look at LiveDatahow to get the version number:

int getVersion() {
    return mVersion;
}

This method is a package access permission method. If I create a new LiveDataclass with the same package name, can I get this value without reflection? Actually this is feasible

// 跟LiveData同包名
package androidx.lifecycle

open class SafeLiveData<T> : MutableLiveData<T>() {

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        // 直接可以通过this.version获取到版本号
        val pictorialObserver = PictorialObserver(observer, this.version > START_VERSION)
        super.observe(owner, pictorialObserver)
    }

    class PictorialObserver<T>(private val realObserver: Observer<in T>, private var preventDispatch: Boolean = false) :
        Observer<T> {

        override fun onChanged(value: T) {
            // 如果版本有差异,第一次不处理
            if (preventDispatch) {
                preventDispatch = false
                return
            }
            realObserver.onChanged(value)
        }

    }
}

The idea behind this tricky approach is:

  • The version number can be obtained by using the access permission of the same package name, without obtaining it through reflection.
  • Determine LiveDatawhether Observerthere is a version difference between the sum and the sum. If there is, it will not respond for the first time, otherwise it will respond.

I personally prefer this approach and have also applied it to actual development. The advantages of this method are: small changes, no need for reflection, no need for HashMapstorage, etc. The disadvantages are: it is somewhat intrusive. If the access rights of the later method are modified or the package name is changed, it will be invalid, but I think This possibility is relatively small. After all, the androidx library has been iterated for so many versions and is relatively stable.

Android study notes

Android performance optimization article: Android Framework underlying principles article: Android vehicle article: Android reverse security study notes: Android audio and video article: Jetpack family bucket article (including Compose): OkHttp source code analysis notes: Kotlin article: Gradle article: Flutter article: Eight knowledge bodies of Android: Android core notes: Android interview questions from previous years: The latest Android interview questions in 2023: Android vehicle development position interview exercises: Audio and video interview questions:https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J
https://qr18.cn/F05ZCM
https://qr18.cn/CQ5TcL
https://qr18.cn/Ei3VPD
https://qr18.cn/A0gajp
https://qr18.cn/Cw0pBD
https://qr18.cn/CdjtAF
https://qr18.cn/DzrmMB
https://qr18.cn/DIvKma
https://qr18.cn/CyxarU
https://qr21.cn/CaZQLo
https://qr18.cn/CKV8OZ
https://qr18.cn/CgxrRy
https://qr18.cn/FTlyCJ
https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/133320396