I’m participating in nuggets Creators Camp # 4, click here to learn more and learn together!

preface

As an Android developer, if you’ve been around Jetpack, you’re probably familiar with LiveData, which is designed in observer mode and often used in conjunction with ViewModel for responsive programming to implement data-driven UI.

Today, let’s learn more about LiveData with the attitude of knowing what it is and why it is.

Method of use

Let’s review how it’s used, with a simple example.

class MyViewModel : ViewModel() {

    //3. Change observed data
    private val _text = MutableLiveData<String>().apply {
        value = "This is JcTest"
    }
    / / 1. Instantiation
    val textLd: LiveData<String> = _text
}


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?).{··· omit code ··· viewModel = ViewModelProvider(this)[MyViewModel::class.java]
        //2. Define and register the observer object
        viewModel.textLd.observe(this, Observer {
            //4. Perform observation actions
            viewBinding.textTv.text = it
            // Data UI binding can be further implemented through DataBinding}}})Copy the code

We can summarize it in four steps:

  1. Instantiate aLiveDataTo define the type of data to be observed.
  2. Defines and registers an observer object, which must be a lifecycle component.
  3. Change the value of the observed data, notifying the corresponding observer.
  4. The observer gets the updated new data and performs the observation action.

Now that we know how to use it, let’s take a closer look at the source code to see how it was designed.

The source code parsing

We follow the method of use step by step analysis.

The constructor

Let’s take a look at its constructor:

public LiveData(T value) {
    mData = value;
    mVersion = START_VERSION + 1;
}

public LiveData(a) {
    mData = NOT_SET;
    mVersion = START_VERSION;
}
Copy the code

The constructor is simple, with only two fields:

  • mData: Observed data.
  • mVersion: Version number, +1 for each data change.

Define and register an observer object

In fact, there are two steps: definition and registration.

define

So let’s look at the definition. We know that the observer pattern has an abstract observer interface, so LiveData is no exception.

public interface Observer<T> {
    /** * called when data has changed, where t is the new data after the change. * /
    void onChanged(T t);
}
Copy the code

In this case, the Observer is the abstract Observer interface, and the onChange(t) method is called when the observed data changes.

In addition, LiveData also wraps the Observer.

private abstract class ObserverWrapper {
    final Observer<? super T> mObserver;

    // indicates whether the observation status is valid
    boolean mActive;

    int mLastVersion = START_VERSION;

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

    abstract boolean shouldBeActive(a);

    /** Whether the life cycle is bound */
    boolean isAttachedTo(LifecycleOwner owner) {
        return false;
    }

    void detachObserver(a) {}/** Changes the state of the observer object */
    void activeStateChanged(boolean newActive) {
        if (newActive == mActive) {
            return;
        }

        // Only the observation data is sent to the active observer object.
        mActive = newActive;

        // Re-count the active observer object
        changeActiveCounter(mActive ? 1 : -1);

        if (mActive) {
            // If the current observer object is active, the observation data is distributed to it.
            dispatchingValue(this); }}}Copy the code

ObserverWrapper is the wrapper class for the observer object, so why wrap the observer object? Think about it.

There are also observer objects that inherit the binding life cycle of ObserverWrapper.

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(a) {
        OnStart () -onstop ()
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
        // If the observer is destroyed, remove the observer directly
        if (currentState == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        // Otherwise, call activeStateChanged() and continue to observe
        Lifecycle.State prevState = null;
        while (prevState != currentState) {
            prevState = currentState;
            activeStateChanged(shouldBeActive());
            currentState = mOwner.getLifecycle().getCurrentState();
        }
    }

    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
        // Determine whether a lifecycle component is bound
        return mOwner == owner;
    }

    @Override
    void detachObserver(a) {
        mOwner.getLifecycle().removeObserver(this); }}Copy the code

LifeCycle is used to obtain the state of the LifeCycle, allowing the observer to bind to the LifeCycle.

registered

Let’s take a look at the Observe method in full.

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    // Check to see if the current thread is in the main thread, and throw an exception if it is in the child thread
    assertMainThread("observe");

    // If the current observer is not active, the observer does not perform the observation action, and returns directly
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        return;
    }

    // Create a new lifecycle bound observer object with the lifecycle component
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    // Check whether the observer already exists in the observer object collection, if so, return the value of the collection directly; If it does not exist, it is added to the collection and null is returned.
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    // If the observer already exists, check to see if its lifecycle component is consistent with the current one. If not, throw an exception
    if(existing ! =null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if(existing ! =null) {
        return;
    }
    // If the observer does not already exist, give it lifecycle awareness
    owner.getLifecycle().addObserver(wrapper);
}
Copy the code

To recap: It checks to see if it is currently in the main thread, and throws an exception if it is in a child thread. The status of the current observer is then checked, and if it is not active, it will not be registered. Create a new lifecycle observer wrapper with the lifecycle component, and then check whether the Wrapper already exists in the observer collection. If it does, return the value of the collection directly, i.e., the registered observer existing. If not, wrapper is added to the collection and NULL is returned. Existing is checked to see if it exists, and if it does, its lifecycle component is checked to see if it is the same as the current one, and if not, an exception is thrown. If existing doesn’t exist, give it life-cycle awareness.

Lifecycle is perceived and captured here through Lifecycle in Jetpack, which I won’t go into here for lack of space.

Update the data

After defining and registering the observer object, the next step is to update the observed data.

LiveData supports two ways to update data, namely setValue() and postValue(). The main difference between these two ways is that setValue() works in the main thread and is executed synchronously, while postValue() works in the child thread. It is executed asynchronously.

Let’s take a look at each.

setValue

Let’s start with setValue:

protected void setValue(T value) {
    // Check if it is in the main thread, and throw an exception if it is not
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    // Distribute data. Passing null will iterate over all observers to distribute data
    dispatchingValue(null);
}
Copy the code

To recap: It first checks if it is in the main thread, and if it is not, it throws an exception. The dispatchingValue method is then called to distribute the data, where null is passed through all the observer objects and the data is then distributed one by one.

postValue

Next, let’s look at the postValue method.

protected void postValue(T value) {
    boolean postTask;
    // Add object lock to keep thread safe
    synchronized (mDataLock) {
        // Use mPendingData to determine whether data is being set
        postTask = mPendingData == NOT_SET;
        // Set the data
        mPendingData = value;
    }
    if(! postTask) {return;
    }
    // Finally switch to the main thread with Handler to perform the task
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            // Set new data
            newValue = mPendingData;
            // Reset mPendingData
            mPendingData = NOT_SET;
        }
        // Finally by calling setValue()setValue((T) newValue); }};Copy the code

As you can see, postValue actually ends up being executed by a Handler that switches to the main thread, and eventually calls the setValue method.

Note: If you call postValue(value) several times before executing the postTask on the main thread, only the last value sent will be distributed.

And if you have postValue and setValue, for example:

liveData.postValue("a");
liveData.setValue("b");
Copy the code

The value “b” is set first, and the main thread overwrites it with the value “A”.

To ensure thread safety, object locking is also used here. If you want to learn more about object locking, check out my other article about Android programmers relearning Synchronized.

Designated observation work

Remember from the last step that we called dispatchingValue to distribute the data, which ultimately calls the notice method.

private void considerNotify(ObserverWrapper observer) {
    // If the observer is not active, no notification is required
    if(! observer.mActive) {return;
    }
 
    if(! observer.shouldBeActive()) { observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    // Notifies the observer of a data change
    observer.mObserver.onChanged((T) mData);
}
Copy the code

To recap: Determine whether the current Observer is active and use the observer.onCHANGED (mData) callback to tell the Observer to perform the observation action.

reflection

Why wrap the observer object

Going back to the above question, why wrap the observer object?

As explained in the beginning, our observer object is defined and registered in the Activity, and the Activity has a life cycle and is destroyed and recycled. So, do we still need to notify an Activity when it is destroyed? Display is not required. Therefore, we need to give it a life cycle state to let LiveData know the life cycle state of the observer object. The activation state often mentioned in the article is from onStart() to onStop(). If the observer is not in the active state, there is no need to notify.

Design patterns

The core concept of LiveData is the observer model:

  • Observed object:LiveDataThe data? throughsetValuewithpostValueTo trigger, which isLiveDataThe inside of theValue.
  • Observer object:observeThe second parameter in the method, implementationObserverInterface.
  • Observe actions: Be specificObserver.onChangedThe contents of the method.

conclusion

Through this article, we reviewed the use of LiveData and looked at its source code step by step according to its use steps. We learned how LiveData defines and registers the observed objects, how to change the observed data, and how to distribute the observed data to the corresponding observer objects according to the life cycle status of the observer. The corresponding source code is also very simple, read a few times, will certainly let you deepen the understanding of LiveData.

In fact, the biggest purpose of sharing articles is to wait for someone to point out my mistakes. If you find any mistakes, please point them out without reservation and consult with an open mind.

In addition, if you think the article is good and helpful to you, please give me a thumbs-up as encouragement, thank you ~