[Android JetPack series] LiveData

I. Preface

1. What is LiveData

LiveData is a data holding class. It has the following characteristics:

  • Data can be subscribed by observers;
  • Be able to sense the life cycle of components (Fragment, Activity, Service);
  • Only when the component is active will the observer be notified of the data update;

2. What LiveData can do for us

  • It can ensure the unification of data and UI. LiveData adopts the observer mode. LiveData is the observed. When the data changes, it will notify the UI.

  • Reduce memory leaks. LiveData can sense the life cycle of the component. When the component is in the destroyed state, the observer object will be cleared. When the Activity stops, it will not cause crash, because when the component is in the inactive state, it will not receive the notice of data change in LiveData.

  • Components and data related content can be updated in real time. When the component is in the foreground, it can receive real-time notification of data changes. When the component is switched from the background to the foreground, LiveData can notify the component of the latest data, so it ensures that the component and data related content can be updated in real time.

  • Solve the configuration change data problem. When the screen rotates, no additional processing is needed to save the data. When the screen direction changes, the components will be recreated. However, the system cannot guarantee that your data can be recovered. When using LiveData to save data, because the data is separated from the component, when the component is recreated, the data still exists in LiveData and will not be destroyed.

  • Resource sharing. If the corresponding LiveData is a single example, data can be shared among the components of the App; for details of this part, please refer to inherit LiveData.

2. Simple use

1. Create LiveData

There are two ways to create LiveData:

  • Directly use MutableLiveData object;
  • Inherits the LiveData class on its own.

1.1. Directly use MutableLiveData

  MutableLiveData<String> data = new MutableLiveData<>();

1.2 inherit LiveData

2. Create and subscribe observers

LiveData registers the observer through the observe() method. The observer uses anonymity here:

// Written in MainActivity
data.observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                Toast.makeText(MainActivity.this,s,Toast.LENGTH_SHORT).show();
            }
        });

Observe() source code analysis:

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        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);
    }

The observe method receives two parameters:

  • One is lifecycle owner with lifecycle.
  • The other is observer < T >.

First, judge whether the current lifecycle of lifecycle owner is Destroyed:

  • If yes, return directly;
  • If it is not equal to Destroyed, new has an internal class of LifecycleBoundObserver object and the construction method has passed in LifecycleOwner and observer with lifecycle. Look at the source code:
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

The LifecycleBoundObserver inherits from the observer wrapper and implements the generic lifecycle observer, while the generic lifecycle observer inherits the LifecycleObserver interface.

From this we can see that the LifecycleBoundObserver class is to associate the Observer with the lifecycle.

Look at this class again. Let's first look at the onStateChanged() method, which will call back when the life cycle changes. If getCurrentState() == DESTROYED, remove observer. Otherwise, call the activeStateChanged() method of the parent class ObserverWrapper:

 private abstract class ObserverWrapper {
        final Observer<T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<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;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }

Analyze activeStateChanged():

  • First, judge whether the old and new states of the ACTIVE state are the same or not. If they are different, assign the new state to the maactive, which is the logical processing when the life cycle state is ACTIVE. If the new state is the same as the old state, it is returned directly.

  • Here is a constant livedata.this.mcactivecount. It can be understood that the observer is in an active state. Number.

  • Look down if (was inactive & & maactive). If mcactivecount = 0 and mActive is true, the observer is active.

    • onActive() is called when the number changes from 0 to 1; the observer is active
    • Call onInactive() when the number changes from 1 to 0

However, onActive(), onInactive(), does not have any implementation code. OK, let's continue to look at dispatching value (this); it should be data change message scheduling. Source code:

private void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

Let's not look at the first few lines of if judgment. Start with if (initiator!= null). If initiator!= null, call considerNotify(initiator) method; source code:

private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

The last line of code is observer.msobserver.onchanged ((T) MDATA); data change callback of observer;

OK, let's go back to the logic of initiator == null:

for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }

If the initiator == null, the ObserverWrapper will be obtained by traversing the iterator mbobservers, and finally the considerNotify method will be called. Since the ObserverWrapper is selected, let's see where it is stored.

Go back to the Observe() method:

 @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        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);
    }

Mbobservers.putifabsent (observer, wrapper) is stored in the container. Mbobservers.putifabsent seems to be a rare way to add data. So it's easy to see what kind of data container mbobservers is. In the member variables:

private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers =
        new SafeIterableMap<>();

SafeIterableMap has the following features:

  • Support key value pair storage, realize with linked list, simulate the interface of Map
  • Support to delete any element during traversal without triggering ConcurrentModifiedException
  • Non thread safe

Finally, addObserver adds the registration.

3. Initiate data notification

There are two ways to initiate data notification:

  • postValue
  • setValue
    data.postValue("Hello LiveData");
 // data.setValue("Hello LiveData");

MutableLiveData source code:

public class MutableLiveData<T> extends LiveData<T> {
    @Override
    public void postValue(T value) {
       super.postValue(value);
    }

   @Override
   public void setValue(T value) {
       super.setValue(value);
   }
 }

Let's first look at the setValue Code:

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }
  private static void assertMainThread(String methodName) {
        if (!ArchTaskExecutor.getInstance().isMainThread()) {
            throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
                    + " thread");
        }
    }

Assert main thread ("setValue"); is to determine whether it is in the main thread.

So it seems that * * setValue mode must be executed in the main thread. If it is not the main thread, an exception will be thrown. * *

Take a look at postValue:

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }

Then postValue calls the posttomaintenthread method, and finally uses setValue method:

    private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            //noinspection unchecked
            setValue((T) newValue);
        }
    };

So finally understand why the setValue method can only be invoked in the main thread, postValue can be invoked in any thread, and if postValue is updated in the back table thread, postValue must be called.

III. advanced application

1. Design and architecture of LiveDataBus

LiveDataBus source code

Composition of LiveDataBus

  • news Messages can be any Object, and different types of messages can be defined, such as Boolean and String. You can also define messages of a custom type.

  • Message channel
    LiveData plays the role of message channel. Different message channels are distinguished by different names. The name is of String type. You can get a LiveData message channel by name.

  • Message bus
    The message bus is implemented by a single example, and different message channels are stored in a HashMap.

  • Subscribe
    Subscribers get the message channel through getChannel, and then call observe to subscribe to the message of this channel.

  • Release
    The publisher gets the message channel through getChannel, then calls setValue or postValue to publish the message.

Keywords: Mobile Fragment

Added by mcdsoftware on Thu, 24 Oct 2019 10:22:33 +0300