Source code analysis of Android arch components -- LiveData

Posted by Galahad on Tue, 15 Feb 2022 03:46:09 +0100

In this article, let's take a look at LiveData in Android architecture components. Compared with ViewModel, LiveData has life cycle awareness, that is, it combines ViewModel with lifecycle. When the data of the application is updated, we generally only want to update the UI when the application is visible to the user; Further, if the application is not visible, we can even stop updating the data. This is the so-called "perceived application life cycle".

Here we mainly focus on the implementation of LiveData. For usage, please refer to Google's course.

Add Observer

When using LiveData, the first thing to do is to add an Observer.

public interface Observer<T> {
    /**
     * Called when the data is changed.
     * @param t  The new data
     */
    void onChanged(@Nullable T t);
}

// Note that he is abstract class
public abstract class LiveData<T> {

    // Only after onStart, the modification of data will trigger observer onChanged()
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {}

    // Whenever the data changes, observer will be triggered onChanged()
    public void observeForever(@NonNull Observer<T> observer) {}
}

Since LiveData is an abstract class, we cannot directly generate its instance. For the owner of data, MutableLiveData can be used:

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

    @Override
    public void setValue(T value) {
        // LiveData.setValue() is a protected method
        super.setValue(value);
    }
}

The so-called owner of data. For example, if you use MVP mode, the data belongs to the Model layer, and the other two layers should not modify the data

By making these two setter methods protected, as long as we return LiveData to the customer, we don't have to worry that the data will be accidentally modified by the customer:

class SomeClass extends ViewModel {

    // MutableLiveData is held inside the class, so we can call postValue/setValue
    private final MutableLiveData<Foo> mYourData = new MutableLiveData<>();

    // What is returned is LiveData. There is no postValue/setValue in the public method of LiveData
    public LiveData<Foo> getData() {
        return mYourData;
    }
}

Using public, protected, private, default access and final can make our design intention clearer.

Now go back to our observe() method. The implementation of observeForever is similar to observe. We won't look at it. Here we only look at observe():

public abstract class LiveData<T> {

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        // The activity has been destroy ed, so there is no need to add 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)) {
            // The same observer can be added again only if the corresponding lifecycle owner is different
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        // The observer added in this way will receive the notification of data update only when the activity is visible,
        // To know when an activity is visible, you need to register with Lifecycle.
        // Because of this, observe() has one more parameter lifecycle owner than observeForever()
        owner.getLifecycle().addObserver(wrapper);
    }

}

Let's continue to look at lifecycle bundobserver:

public abstract class LiveData<T> {

    // Null implementation. If you want to stop updating data after LiveData becomes inactive, you can
    // These two methods
    protected void onActive() {}
    protected void onInactive() {}

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

        ObserverWrapper(Observer<T> observer) {
            mObserver = observer;
        }

        // Returns true if observer is in active state
        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;
            // LiveData. this. M activecount indicates the number of observer s in active state
            // When m activecount is greater than 0, 'LiveData' is active
            // Pay attention to distinguish between the active state of observer and the active state of LiveData
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                // inactive -> active
                onActive();
            }
            // It's better to use else if here, because only one will execute. Else if is easier to read
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                // M activecount is equal to 1 before we modify it, that is, 'LiveData' is from active
                // The status changes to inactive
                onInactive();
            }
            if (mActive) {
                // The customer may not need to get the latest data from the active server at this time
                // We'll look at his realization in the next section
                dispatchingValue(this);
            }
        }
    }

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;

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

        @Override
        boolean shouldBeActive() {
            // onStart to onStop are considered to be in active state
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        // This is the callback function of lifecycle
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            // When an instance of LifecycleBoundObserver is just generated, active = = false, register with
            // After Lifecycle, Lifecycle will synchronize the status to us (that is, call back this function).
            // Readers who are not familiar with lifecycle can read
            // https://jekton.github.io/2018/07/06/android-arch-lifecycle/
            activeStateChanged(shouldBeActive());
        }

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

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

Here, we'll finish the registration of observer. Let's see how to publish data to LiveData.

Publish modification

There are two ways to modify LiveData:

public abstract class LiveData<T> {

    // Synchronous modification of data
    protected void setValue(T value);

    // It will use handler to post a runnable, and then set value in runnable
    protected void postValue(T value);
}

SetValue is relatively simple. Let's look at setValue first:

public abstract class LiveData<T> {

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        // Every time value is updated, mVersion + 1 will be changed
        // ObserverWrapper also has a field called mLastVersion
        // By comparing these two fields, you can avoid repeatedly notifying customers (see details later)
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

    // If initiator == null, it means to notify all observer s
    // If it is not equal to null, only the initiator will be notified
    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            // The data modification is triggered in the callback of observer
            // After setting mDispatchInvalidated to true, you can let the following loop know
            // The data was modified to start a new iteration.
            //
            // For example, dispatchingvalue - > observer onChanged -> setValue
            //            -> dispatchingValue
            // The return here is the dispatchingValue in the back, and then in the first
            // dispatchingValue will re traverse all observer s and call their
            // onChanged. 
            //
            // If you want to avoid this situation, you can use postValue in the callback to update the data
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                // Call observer onChanged()
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        // A customer updates the data in the callback. After the break, the for loop will
                        // restart
                        break;
                    }
                }
            }
        // When a customer updates data in the callback, mDispatchInvalidated == true
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
}

Readers who have read my life cycle source code analysis should be familiar with the way dispatchingValue handles loop calls. Take this as an example. In order to prevent circular calling, we set a flag (mDispatchingValue) before calling the customer code, and then set it to false. If this method is triggered in the callback, it can be detected through mDispatchingValue.

After the circular call is detected, set the second flag (mDispatchInvalidated) and return. The return will return to the previous call. The previous call knows that the data has been modified by checking mDispatchInvalidated, so it starts a new round of iteration.

Here is considerNotify:

    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;
        }
        // For LifecycleBoundObserver, even if the data of 'LiveData' does not change, as long as the life of the activity
        // Has the cycle changed, or may considerNotify be called multiple times
        // By comparing observer Mlastversion and mVersion, you can know whether the observer has the latest data
        //
        // In fact, observer Mlastversion can only be equal to mVersion at most
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

}

After reading setValue, postValue is very simple for us:

public abstract class LiveData<T> {

    // Note that he is volatile. Because postValue can be called from the background thread,
    private volatile Object mPendingData = NOT_SET;

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


    protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            // There is already a runnable that has not been executed after post, so there is no need to post,
            // When the runnable of the previous post is executed, the newly set value will be obtained
            return;
        }
        // The final execution is handler post()
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
}

We have finished reading the core code of LiveData. In fact, its implementation is also very simple, right?

summary

There are two things worth learning about LiveData, one is the processing of circular calls, and the other is the use of mVersion. About mVersion, here is another example encountered in previous work. When the background thread persists the data (this thread copies a copy of the data), the data may also be updated. In order to judge whether the saved data is up-to-date, my practice at that time was to introduce something similar to mVersion. Every time I modify the data, I add 1 to mVersion. By comparing mVersion with the version of the saved data, you can know whether the latest data is saved (of course, it is better to tell the background thread that the data has been modified and ask him to retrieve the data again).

Topics: Java Android