This is the fourth day of my participation in Gwen Challenge

Many interviewers like to ask a lot of questions.

For example, a small LiveData postValue might ask a series of questions:

PostValue with setValue

PostValue, like setValue, is a method used to update LiveData data:

  • SetValue can only be called on the main thread to update data synchronously
  • PostValue can be called from a background thread, which internally switches to the main thread to call setValue
liveData.postValue("a");
liveData.setValue("b");
Copy the code

In the code above, A is updated after B.

PostValue could not receive the notification

Notification of received data changes may occur due to improper use of postValue:

If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.

As noted above, the source code notes explicitly that when postValue is called consecutively, it is possible to receive only the last data update notification.

Combing the source code can understand the reasons:

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

After mPendingData is successfully assigned a value, a Runnable is posted

The mPostValueRunnable implementation is as follows:

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run(a) {
        Object newValue;
        synchronized(mDataLock) { newValue = mPendingData; mPendingData = NOT_SET; } setValue((T) newValue); }};Copy the code
  • PostValue stores data to mPendingData, and mPostValueRunnable consumes mPendingData in the UI thread.

  • A new Runnable will not be posted, even if it postvalues continuously, before the mPendingData value in Runnable has been consumed

  • The production (assign) and consumption (assign NOT_SET) of mPendingData needs to be locked

This is why only the last notification is received when postvalues are consecutively posted.

The source code has been combed, but why is it designed this way?

Why does Runnable post only once?

Why does Runnable wait until the end to post once, rather than post every time there is data in mPenddingData that is constantly updated?

One interpretation is that for the sake of performance, the UI displays only the final state, leaving out the frequent refresh caused by the intermediate state. That may be one of the design goals, but a more plausible explanation is that it doesn’t make sense to post multiple times, so just post once

We know that for setValue, the data is updated in sequence when called multiple times in a row:

As follows, subscriber receives notification from A and B at once

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

Observer#onChanged() is called synchronously from dispatchingValue() to inform subscribers in turn:

/ / setValue source code

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

But for postValue, if when the value changes, we post immediately without blocking

protected void postValue(T value) {
    mPendingData = value;
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run(a) { setValue((T) mPendingData); }};Copy the code
liveData.postValue("a")
liveData.postValue("b")
Copy the code

Because of the overhead of thread switching, if postValue is called continuously, only B and B can be received, but not A.

Therefore, Posting multiple times is meaningless, but once is fine.

Why add read/write lock?

As you already know, whether or not to post depends on whether mPendingData is NOT_SET. Because mPendingData is accessed in a multithreaded environment, it is not thread-safe without a read/write lock.

protected void postValue(T value) {
    boolean postTask = mPendingData == NOT_SET; / / - 1
    mPendingData = value; / / - 2
    if(! postTask) {return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run(a) {
        Object newValue = mPendingData;
        mPendingData = NOT_SET; / / - 3setValue((T) newValue); }};Copy the code

As above, if 3 is performed between 1 and 2, the values set in 2 will not be updated

Replace LiveData with RxJava

How do I avoid missing any notifications in a multi-threaded environment? A better idea is to use a streaming framework like RxJava, where any updates are sent as a stream so that they are not lost.

fun <T> Observable<T>.toLiveData(a): LiveData<T> = RxLiveData(this)

class RxLiveData<T>(
    private val observable: Observable<T>
) : LiveData<T>() {
    private var disposable: Disposable? = null

    override fun onActive(a) {
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                setValue(it)
            }, {
                setValue(null)})}override fun onInactive(a){ disposable? .dispose() } }Copy the code

The last

A streaming framework such as RxJava is needed to ensure the sequence and integrity of events during thread switching.

Sometimes interviewers will ask questions to explore the technical depth of the candidate, so when preparing for the interview, it is important to ask yourself a lot of questions and know why.

Of course, I don’t agree with this kind of interrogation, especially when it comes to details that have no practical value. Therefore, this article also reminds the majority of the interviewer, while digging depth, should pay attention to the discretion, do not ask questions with the goal of stumping the candidate.