Introduction and Use

LiveData is officially defined as: an observable data store class; Has the following advantages:

  • The observer mode ensures that the interface conforms to the data state without updating the interface when the data changes.
  • With life cycle awareness, there is no need to manually process the life cycle, nor will the Activity stop and cause a crash;
  • The observer is bound to Lifecycle and will be cleaned up automatically without memory leaks; Lifecycle can only be bound to one observer;
  • Data is always in the latest state, the life cycle becomes inactive, and it displays the latest data when it becomes active again (the latest data is displayed when the Activity life cycle changes and it returns to the active state)

Basic usage:

Create:

class LiveDataViewModel : ViewModel() {
    val currentCount: MutableLiveData<Int> by lazy {
        MutableLiveData(0)}}Copy the code

Use:

class LiveDataActivity : AppCompatActivity() {
    val viewModel: LiveDataViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data)
        val textView = findViewById<TextView>(R.id.tv1)
        // Add a listener to observe the LiveData object
        viewModel.currentCount.observe(this, Observer<Int> {
            textView.text = "$it"
        })
        findViewById<Button>(R.id.button).setOnClickListener {
        	// Update LiveData object dataviewModel.currentCount.value = viewModel.currentCount.value? .plus(1)}}}Copy the code

Source code analysis

Advantages of LiveData introduced at the beginning:

  • Observer model;
  • Life cycle perception ability;
  • Data is always up to date.

We mainly analyze LiveData from these three directions.

The observer

The definition of an observer in LiveData is very simple. The interface defines only one interface:

public interface Observer<T> {
    void onChanged(T t);
}
Copy the code

The onChanged method is called whenever the data changes. However, the Observer is mostly implemented as an anonymous inner class, which implements the onChanged method to perform post-operations after data changes.

LiveData provides two methods for registering observers:

  • observe(LifecycleOwner , Observer)
  • observeForever(Observer)

The main difference between the two methods is that the former needs to be passed as LifecycleOwner, which provides lifecycle safety and removes the observer when the component is destroyed.

All registered observers are stored in a SafeIterableMap, which is a chained phenotype structure but provides an API for Map key-value pairs. It is declared in LiveData and assigned when declared:

private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = 
	new SafeIterableMap<>();
Copy the code

SafeIterableMap does not store observers directly, but instances of their wrapper class, ObserverWrapper.

Observe (LifecycleOwner, the Observer) and observeForever (Observer) through SafeIterableMap. PutIfAbsent method (to the Observer the Observer for the Key, Add data to ObserverWrapper as value:

public V putIfAbsent(@NonNull K key, @NonNull V v) { Entry<K, V> entry = get(key); if (entry ! = null) { return entry.mValue; } put(key, v); return null; }Copy the code

The method is simple, but has two key features:

  1. If the key of the added data already exists in the list, return its value;
  2. Returns NULL if the key of the added data is not in the list.

That is, adding the same Observer repeatedly returns the ObserverWrapper instance passed in when it was first added as a Key value.

Add (register) observer methods have specific source code as follows:

The code for the observe(LifecycleOwner, Observer) method is as follows:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? Super T> observer) {// Check whether the main thread is assertMainThread("observe"); Lifecycle().getCurrentState() == DESTROYED) {return; Lifecycle(owner.getLifecycle().getCurrentState() == DESTROYED) {return; } // Build a wrapper class LifecycleBoundObserver wrapper for the observer = new LifecycleBoundObserver(owner, observer); // Add the wrapper to the list ObserverWrapper existing = mobServers.putifAbsent (observer, wrapper); // If the Observer has already been added and the Observer is already bound to another LifecycleOwner, throw an exception if (existing! = null && ! existing.isAttachedTo(owner)) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } // The observer has been added and is already bound to the current LifecycleOwner if (existing! = null) { return; } owner.getLifecycle().addobServer (wrapper); }Copy the code

This method first performs some validation on the passed Observer, detailed in the comment. Since this method requires the caller to pass in a LifecycleOwner instance, it means that it has the ability to sense the lifecycle. LifeCycle will determine whether an Activity or Fragment that LifeCycle is attached to will be active and will inform the observer of data changes only if it is active. Also, when the component goes into a DESTROYED state, it destroys the observer unbind to avoid a possible memory leak.

The observeForever(Observer) method is relatively simple:

public void observeForever(@NonNull Observer<? Supert > observer) {assertMainThread("observeForever"); // Create a wrapper class for the observer AlwaysActiveObserver Wrapper = new AlwaysActiveObserver(observer); // Add the wrapper to the list ObserverWrapper existing = mobServers.putifAbsent (observer, wrapper); The observe(LifecycleOwner, Observer) method has been called as an argument if the Observer Observer has been added and the wrapper class is LifecycleBoundObserver. Directly throw an exception if (existing instanceof LiveData. LifecycleBoundObserver) {throw new IllegalArgumentException (" always add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } / / active trigger activeStateChanged method wrapper. ActiveStateChanged (true); }Copy the code

The detailed process is also written in the comments and will not be explained.

The summary is as follows:

  • LiveData stores Observe via a linked list;
  • LiveData provides two methods to add Observe, one lifecycle safe and one unsafe;
  • Observe can bind only one lifecycle component and cannot call the observeForever method once bound.

Life cycle awareness

As mentioned in the previous section, LifecycleBoundObserver and AlwaysActiveObserver differ in whether or not LifeCycle is bound. Observers bound by observe(LifecycleOwner, Observer) have lifecycle awareness. Here’s a look at how Observe (LifecycleOwner, Observer) gives the Observer the ability to perceive the lifecycle using the source code.

Observe (LifecycleOwner, Observer) observeForever(Observer) They use LifecycleBoundObserver and AlwaysActiveObserver, respectively, as wrapper classes for the observer, differing in whether or not they are bound to the LifeCycle component. The source code for ObserverWrapper is as follows:

Private Abstract class ObserverWrapper {// Final Observer<? super T> mObserver; // Flag if mObserver is active Boolean mActive; Int mLastVersion = START_VERSION; // Mark the data version number for comparison with LiveData. ObserverWrapper(Observer<? super T> observer) { mObserver = observer; } // Determine whether the observer's host is active (LifecycleOwner). LifecycleOwner Boolean isAttachedTo(LifecycleOwner owner) {return false; } // Remove observer void detachObserver() {} void activeStateChanged(Boolean newActive) {if (newActive == mActive) {return; } mActive = newActive; changeActiveCounter(mActive ? 1:1); If (mActive) {dispatchingValue(this); }}}Copy the code

ObserverWrapper is an abstract class that holds an instance of the Observer and defines the abstract methods and common logic. These methods are mainly used to judge the state of the observer. At the same time, LifeCycle is listened to as an observer. Perform lifecycle callback and processing.

LifecycleBoundObserver subclass ObserverWrapper when adding an Observer using the observe(LifecycleOwner, Observer) method:

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() {LifeCycle is considered to be active when LifecycleOwner's LifeCycle is STARTED and RESUMED mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); } @Override public void onStateChanged(@NonNull LifecycleOwner source, @nonnull Lifecycle.Event Event) {// Whenever the LifecycleOwner Lifecycle changes, LifeCycle.State currentState = is called back when LifecycleOwner's LifeCycle is in a DESTROYED State mOwner.getLifecycle().getCurrentState(); if (currentState == DESTROYED) { removeObserver(mObserver); return; } // Compare current State, get current Lifecycle State, // call activeStateChanged Lifecycle.State prevState = null; while (prevState ! = currentState) { prevState = currentState; activeStateChanged(shouldBeActive()); currentState = mOwner.getLifecycle().getCurrentState(); }} @override Boolean isAttachedTo(LifecycleOwner owner) {if mOwner is not null, LifecycleOwner return mOwner == owner; } @override void detachObserver() {// Touch the lifecycle component listener mowner.getLifecycle ().removeObserver(this); }}Copy the code

Lifecyclebound Server implements the LifecycleEventObserver interface, This makes it possible to bind Lifecycle in the observe(LifecycleOwner, Observer) method via owner.getLifecycle().addobServer (Wrapper). Here again, the LifecycleBoundObserver implements a listener for the Lifecycle component. Whenever Lifecycle change, LifecycleBoundObserver. OnStateChanged method will be invoked. When the lifecycle state is DESTROYED, the removeObserver method to remove the LiveData listener is called:

public void removeObserver(@NonNull final Observer<? Supert > observer) {assertMainThread("removeObserver"); Remove (observer); // Remove ObserverWrapper removed = mobServers.remove (observer); if (removed == null) { return; } // remove ObserverWrapper removal.detachobServer (); removed.activeStateChanged(false); Public void removeObservers(@nonnull final LifecycleOwner owner) { assertMainThread("removeObservers"); for (Map.Entry<Observer<? super T>, ObserverWrapper> entry : mObservers) { if (entry.getValue().isAttachedTo(owner)) { removeObserver(entry.getKey()); }}}Copy the code

After removing the LiveData observer, removeObserver calls the detachObserver method of ObserverWrapper, The implementation of this method in LifecycleBoundObserver is to unlisten on the LifeCycle component.

The AlwaysActiveObserver used by the observeForever(Observer) method is much simpler:

private class AlwaysActiveObserver extends ObserverWrapper { AlwaysActiveObserver(Observer<? super T> observer) { super(observer); } @Override boolean shouldBeActive() { return true; }}Copy the code

ShouldBeActive returns true, which means that the observer is notified whenever the data changes.

The summary is as follows:

LiveData manages lifecycle aware observers by holding its own observers through a wrapper class and by listening to lifecycle components through instances of the wrapper class. Not only are data changes notified when the lifecycle changes occur (e.g. the device rotates, which immediately sends the latest available data to the observer), but life cycle awareness is achieved by unbinding implementation when the lifecycle is in a DESTROYED state.

Maintain the state of the data

LiveData not only notifies registered observers of data changes, it is possible to trigger a callback when the observer is registered to return the latest value to the observer:

For example:

viewModel.currentCount.observe(this, Observer<Int> {textView1.text = "$it"}) findViewById<Button>(R.i.button).setonClickListener {// Update LiveData object data viewModel.currentCount.value = viewModel.currentCount.value? .plus(1) viewModel.currentCount.observe(this, Observer<Int> { textView2.text = "$it" }) }Copy the code

Although textView2 registered the observer to listen after the LiveData data changed. But it still gets the same value as textView1.

First look at the logic when updating values:

Data update

LiveData provides two methods for updating values:

  • SetValue: can only be called from main thread /UI
  • PostValue: can be called in a non-UI thread

The code for setValue is as follows:

// Flag LiveData is sending data to the observer private Boolean mDispatchingValue; Private Boolean mDispatchInvalidated; protected void setValue(T value) { assertMainThread("setValue"); // update the version number of Value to compare with that of the observer mVersion++; mData = value; dispatchingValue(null); } void dispatchingValue(@nullable ObserverWrapper initiator) {if (mDispatchingValue) { MDispatchInvalidated = true; mDispatchInvalidated = true; return; } // Start sending data, update status flag mDispatchingValue = true; do { mDispatchInvalidated = false; if (initiator ! = null) { considerNotify(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; If (observer.mLastVersion >= mVersion) {return; } observer.mLastVersion = mVersion; / / notification data update observer. MObserver. OnChanged (mData (T)); }Copy the code

The code flow is very simple and it is necessary to talk about whether data is being sent or not in dispatchingValue. When data is being updated, the updated status and invalid data are marked using mDispatchingValue and mDispatchInvalidated respectively. In this way, old data can be discarded and new data can be sent. A typical scenario is as follows:

When there are two observers A, B, and C, the setValue method is called in observer A’s onChanged method. At this time, if setValue is used to update data, LiveData will be marked as sending data by mDispatchingValue. When A gets data, it will immediately trigger dispatchingValue by setValue. At this point, a new loop will be entered to send new data to the observer. However, there is A problem. Only observer A receives the old data, and only observer B and C receive the new data once. In other words, LiveData will vary in the way observers receive data (some observers may miss some data), but as a result, they will all get the latest data.

Verify with code as follows:

val currentCount: MutableLiveData<Int> by lazy {MutableLiveData()}

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main_messenger)
   
   currentCount.observe(this) {
   Log.e("A","------$it")
      if (it == 1){
         currentCount.value = 2
      }
   }
   currentCount.observe(this) {
      Log.e("B","------$it")
   }
   currentCount.observe(this) {
      Log.e("C","------$it")
   }
   currentCount.value = 1
}
Copy the code

The output is:

A: ------1
A: ------2
B: ------2
C: ------2
Copy the code

Then the postValue method, which does not force the caller to be in the main thread. It allows you to update data in child threads.

volatile Object mPendingData = NOT_SET; private final Runnable mPostValueRunnable = new Runnable() { @SuppressWarnings("unchecked") @Override public void run() { Object newValue; synchronized (mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); }}; protected void postValue(T value) { boolean postTask; PostTask = mPendingData == NOT_SET; synchronized (mDataLock) {postTask = mPendingData == NOT_SET; // Make sure the temporary value is the latest mPendingData = value; } // If there is a data update (indicating that the data is being updated), there is no need to switch the thread again. postTask) { return; } ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); }Copy the code

Finally call the postToMainThread method of DefaultTaskExecutor:

public void postToMainThread(Runnable runnable) { if (mMainHandler == null) { synchronized (mLock) { if (mMainHandler ==  null) { mMainHandler = createAsync(Looper.getMainLooper()); } } } mMainHandler.post(runnable); }Copy the code

The code has three more points than setValue:

  • Locking to ensure thread safety;
  • The thread switch is implemented via Handler+ Runnable and the setValue method is called. The next step is the same as setValue.
  • PostValue first stores the data to mPendingData, then switches threads via Runnable, and calls setValue from within Runnable to send the saved mPendingData to observers. Because mPendingData uses volatile modifier, it simply changes its value without starting Runable. This is where data is lost.

How do I ensure the latest values

In the previous article, a variable mVersion was mentioned repeatedly. It is declared and initialized in LiveData as follows:

public abstract class LiveData<T> { private int mVersion; public LiveData(T value) { mData = value; mVersion = START_VERSION + 1; } /** * Creates a LiveData with no value assigned to it. */ public LiveData() { mData = NOT_SET; mVersion = START_VERSION; }}Copy the code

It marks the version number of the data and changes the data every time setValue is called. It all performs the mVersion++ operation:

protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}
Copy the code

The ObserverWrapper also maintains a variable that marks the version number of the data an observer receives:

private abstract class ObserverWrapper {
     int mLastVersion = START_VERSION;
}
Copy the code

Whenever data changes, the notice method is called to sync the version number:

private void considerNotify(ObserverWrapper observer) { if (! observer.mActive) { return; } if (! observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) { return; } observer.mLastVersion = mVersion; observer.mObserver.onChanged((T) mData); }Copy the code

The Observe (LifecycleOwner, Observer) method is slightly different from observeForever as to why observers registered after the setValue call also get the latest data.

The former adds lifecycle listening owner.getLifecycle().addobServer (wrapper); Triggered when LifecycleBoundObserver. OnStateChanged triggering activeStateChanged method. ObserveForever is more abrasive and directly triggers activeStateChanged:

public void observeForever(@NonNull Observer<? super T> observer) { assertMainThread("observeForever"); AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer); ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); if (existing instanceof LiveData.LifecycleBoundObserver) { throw new IllegalArgumentException("Cannot add the same observer" + " with different lifecycles"); } if (existing ! = null) { return; } wrapper.activeStateChanged(true); }Copy the code

summary

  • LiveData updates data through setValue and postValue. The former does not support multi-threading.
  • LiveData uses the int value as the version number to compare with the observer to ensure that the data is in the latest state.
  • LiveData only ensures that all observers have access to the latest data. SetValue will cause some observers to fail to receive expired data, and postValue method will discard old data.

Summary and reflection

There are many articles on the web about ViewModel+LiveData replacing RxJava. Here is my understanding of LiveData and RxJava (RxAndroid). Start with the argument that the two duties serve different purposes, and strictly speaking, you shouldn’t be talking about who replaces who (more on why strict should be added later in this article).

Personally, Rx is designed to make it as easy to handle asynchronous operations as it is to handle synchronous operations.

However, Google officially gave LiveData a clear definition:

LiveData is an observable data holder class

LiveData is an observable data store class!! It also ensures that the observer gets the latest data. However, there is no guarantee that the percentage of data will be transferred, and data will be lost and different observers will get different callbacks.

They both have their own uses and should not be compared side by side. Rx is mainly used to help us sort out logic and simplify business code. LiveData stores the data for us.

RxJava is called Reactive Extensions for the JVM. It can be understood as a responsive programming extension to Java. It’s different from generic libraries like Retrofit, OkHttp, etc. Instead of giving us examples of how to solve specific problems, Rx gives us the logic to make asynchrony as simple as synchronization. Its observer, Observable, you can also think of it as a CallBack. The difference is that upstream data producers simply send data via an Observable, either asynchronously or synchronously or several times. Downstream observers should process and not care about how the data was generated. And all the callbacks are on the same line, kind of like a river, or flow.

Let’s go back to how we use it in Android: most of it is to make a network request and switch threads. You don’t even need a few operators. If so, then of course it can be replaced. But is it its fault? No, look at what Rx was designed for, and why are you using it? What you see as its advantages, can it be replaced with sensible code design? Ask yourself, have you considered other options before using this library? How much do you need to use it and how much do you want to follow suit? If you are using it to make a network request, then of course it can be replaced. Because coroutines plus LiveData can be done better and easier to get started.

So in the selection of technology, we must fit the business and reality, do not blindly cite.