Update to add a cause analysis and solution for LiveData sticky messages

1.LiveData

LiveData is an observable data storage class. Unlike regular observable classes, LiveData is lifecycle aware, meaning that it follows the lifecycle of other application components, such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state. If the life cycle is in the STARTED or RESUMED state, LiveData considers the observer to be active. LiveData will only notify active observers of updates. Inactive observers registered to observe LiveData objects do not receive change notifications.

Principle 2.

With the Lifecycle component implementation, the Observe method needs to pass an Observer

2.1 observe method

Encapsulate the Observer and add it as an Observer for the Lifecycle Lifecycle

public void observe(LifecycleOwner owner, Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } // The LifecycleBoundObserver indirectly implements the LifecycleObserver interface, LifecycleBoundObserver Wrapper = New LifecycleBoundObserver(owner, observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing ! = null && ! existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } owner.getLifecycle().addObserver(wrapper); }Copy the code

2.2 LifecycleBoundObserver# onStateChanged

The actual implementation of Observe added overwrites Lifecycle’s onStateChanged method to distribute data while active

public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); If (currentState == DESTROYED) {removeObserver(mObserver); return; } Lifecycle.State prevState = null; while (prevState ! = currentState) { prevState = currentState; activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); }} void activeStateChanged(Boolean newActive) {if (newActive == mActive) {return; } // immediately set active state, so we'd never dispatch anything to inactive // owner mActive = newActive; changeActiveCounter(mActive ? 1:1); If (mActive) {dispatchingValue(this); }}Copy the code

2.3 Data distribution to observers

There are two opportunities for data distribution:

  • When actively calling the setValue or postValue (also called setValue) method of LiveData
  • Called in the Observer’s onStateChanged method when this change occurs in the lifecycle state of the life component holding LiveData
// In the first case, the argument is null, Void dispatchingValue(@nullable ObserverWrapper Initiator) {if (mDispatchingValue) { mDispatchInvalidated = true; return; } mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator ! = null) {// In the second case, let the Observer assign data by notice (initiator); initiator = null; } else {for (Iterator< map.entry <Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { considerNotify(iterator.next().getValue()); if (mDispatchInvalidated) { break; } } } } while (mDispatchInvalidated); mDispatchingValue = false; } private void considerNotify(ObserverWrapper observer) { if (! observer.mActive) { return; } if (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } // The observer data version is ahead, do not update // the mVersion is changed in setValue, If (observer.mLastVersion >= mVersion) {return; if (observer.mVersion >= mVersion) {return; } observer.mLastVersion = mVersion; / / call the method we observe the code passed in the observer, mObserver. OnChanged (mData (T)); }Copy the code

3.LiveData Sticky messages

3.1 Causes of Sticky messages

For example, if you are using activityViewModels in a Fragment page to get a ViewModel for data sharing, you can use activityViewModels to share data. Send a network request on a page, set an errorMsg in LiveData through ViewModel when the network request fails, and when we close the page and enter the page again, without any operation, we will receive the message that the request failed first. This is what LiveData’s sticky messages cause.

Since the ViewModel declaration cycle we get from activityViewModels is longer than the Fragment’s lifetime, the ViewModel is not destroyed with the Fragment when we first close the Fragment. Naturally, LiveData is still around. LifecycleBoundObserver is the actual type when we register an observer for LiveData via vm.observe() when we enter the Fragment once.

Here’s a closer look at the code added by the LiveData Observer.

Public void Observe (@nonnull LifecycleOwner owner, @nonnull Observer<? super T> observer) { assertMainThread("observe"); if (owner.getLifecycle().getCurrentState() == DESTROYED) { // ignore return; } // 1.1, create LifecycleBoundObserver LifecycleBoundObserver Wrapper = new LifecycleBoundObserver(owner, observer); // mObservers is a Map ObserverWrapper Existing = mobsers. putIfAbsent(observer, wrapper); if (existing ! = null && ! existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } // 1.3, register the LifecycleBoundObserver created as an observer of Lifcycle owner.getLifecycle().addobServer (wrapper); } // 2, private abstract class ObserverWrapper {final observer <? super T> mObserver; // obserber Boolean mActive; int mLastVersion = START_VERSION; ObserverWrapper(observer <? super T> observer) { mObserver = observer; } abstract boolean shouldBeActive(); boolean isAttachedTo(LifecycleOwner owner) { return false; } void detachObserver() { } void activeStateChanged(boolean newActive) { if (newActive == mActive) { return; } // immediately set active state, so we'd never dispatch anything to inactive // owner mActive = newActive; changeActiveCounter(mActive ? 1:1); If (mActive) {dispatchingValue(this); }}} // 3, inherit from ObserverWrapper, hold the Observer passed by the user, Also implement LifecycleEventObserver to sense the life cycle class LifecycleBoundObserver extends ObserverWrapper Implements LifecycleEventObserver { @NonNull final LifecycleOwner mOwner; LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) { super(observer); mOwner = owner; } @Override boolean shouldBeActive() { return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @override public void onStateChanged(@nonnull LifecycleOwner source, @NonNull Lifecycle.Event event) { Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { removeObserver(mObserver); return; } // Lifecycle becomes active or inactive.State prevState = null; // Lifecycle becomes active or inactive. while (prevState ! = currentState) { prevState = currentState; // Parent method activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); } } @Override boolean isAttachedTo(LifecycleOwner owner) { return mOwner == owner; } @Override void detachObserver() { mOwner.getLifecycle().removeObserver(this); }}Copy the code

You can see that when you add an Observer to LiveData, you internally create a LifecycleBoundObserver that inherits from the ObserverWrapper class and implements the LifecycleEventObserver interface. The ObserverWrapper internally holds the Observer we pass, while LifecycleEventObserver is an Observer at Lifecycle, its onStateChanged method will be called back when the Lifecycle changes, Thus our registered Observer is aware of the life cycle and triggers the data segmentation of LiveData only if the life cycle is active.

The timing of the distribution of LiveData is mentioned in section 2.3 above. One is triggered when we change the LiveData by setValue to distribute all obserbers, the other is triggered within the Observer. That is, the onStateChanged method is triggered when the LifecycleBoundObserver senses a change in the life cycle, where the activeStateChanged method is invoked to distribute data when the life cycle is detected to be active.

At this point we can understand the reasons for the sticky data in LiveData: The second time the Fragment entered the page, the registered Observer detected that the life cycle had become active and remembered that the previous data had been distributed, so the second time we entered the Fragment, we received the data without doing anything. (One more point about Lifecycle is that Lifecycle distributes previous Lifecycle events to post-registered observers, so even if the Lifecycle component is active when we register LiveData observers, But the observer (LifecycleBoundObserver) can still receive the callback that becomes active).

3.2 Processing of Sticky messages

The reasons for sticky messages in LiveData have been described above, but here’s the solution.

Open class Event<out T>(private val content: T) { var hasBeenHandled = false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } fun peekContent(): Class EventObserver<T>(private val onEventUnhandledContent: private val onEventUnhandledContent: private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> { override fun onChanged(event: Event<T>?) { event? .getContentIfNotHandled()? . Let {onEventUnhandledContent (it)}}} / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- when using -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- val l = EventObserver<Event<String>>() l.observe(this, Observer { })Copy the code

reference

  • Network request state encapsulation practically
  • The observer method is repeatedly called when MutableLiveData is a global variable
  • “Finally get it” series: Jetpack AAC complete analysis (2) Fully master LiveData!
  • Solutions to the problems caused by the LiveData stickiness event