This is the first day of my participation in the First Challenge 2022

One day,

I was listening to the song and typing code happily when Comrade Wang rushed over and patted me on the shoulder.

“Fu boss, I have a problem, two consecutive requests for the same interface, different parameter transmission, but liveData onChange callback only went once, only one place on the UI interface update success, this is why ah?”

When I heard it was buggy, I was so excited, I took off my headphones,

“Come on, code.”

You register a listener at the UI layer, and then postValue the interface twice using livedata. It looks like postValue is behind this. No hurry, let me simulate the scenario and see his internal implementation.”

Create a new ViewModel, create a MutableLiveData of a String value, throw a method to update the String value, and use the postValue of liveData to tell the UI to update the data.

class MyViewModel : ViewModel() {

    val countLiveData = MutableLiveData<String>()
    var title: String = ""

    /**
     * livedata postValue
     */
    fun updateTitlePost(str:String) {
        title = str
        countLiveData.postValue(title)
    } 
}
Copy the code

Register an Observer directly on the activity and call the viewModel method to update the String value twice in button click.

viewModel = ViewModelProvider(this).get(MyViewModel::class.java) viewModel.countLiveData.observe(this,object :Observer<String>{ override fun onChanged(t: String?) { Log.d(TAG, "livedata onChanged: Value = $t")}}) /** * * the second update value of 'Kotlin * / the findViewById < Button > (R.i db utton). SetOnClickListener {viewModel. UpdateTitlePost (" Java ") viewModel.updateTitlePost("Kotlin") }Copy the code

Click on the mock interface request and call it twice in succession, updating the first value to ‘Java’ and the second value to ‘Kotlin’.

Run it and see,

No matter how many times you click on the log output of the console, only Kotlin’s value is updated successfully, that is, it conforms to comrade Wang’s scene. Where is the Java value updated for the first time?

Why is the postValue value lost twice in a row?

With that in mind, let’s take a look at the source code inside postValue.

/** * Posts a task to a main thread to set the given value. So if you have a following code * executed in the main thread: * <pre class="prettyprint"> * liveData.postValue("a"); * liveData.setValue("b"); * </pre> * The value "b" would be set at first and later the main thread would override it with * the value "a". * <p> *  If you called this method multiple times before a main thread executed a posted task, only * the last value would be dispatched. * * @param value The new value */ 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

It’s worth noting that the postValue comment says,

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

Despite the official explanation, we still need to be clear about how it works.

PostValue maintains a Boolean postTask, uses synchronized to lock an objec object, mDataLock, and contains a global variable, mPendingData, which is the key to the problem. Each time postValue assigns a new value to mPendingData, the value is then distributed in a Runnable, and the Runnable task is published to the main thread using ArchTaskExecutor. This is why postValue is always updated on the main thread.

I traced it through the breakpoint

viewModel.updateTitlePost("Java")
viewModel.updateTitlePost("Kotlin")
Copy the code

This two-step request route. The first call to a viewModel. UpdateTitlePost (” Java “), postValue internal value is as follows:

The value passed in is “Java”, mPendingData equals the initial value, postStask is true, Directly to the ArchTaskExecutor. GetInstance () postToMainThread (mPostValueRunnable) method. Remember that the value of mPendingData at this time is “Java”,

I continue to use breakpoints to perform the next step, only to find direct execution the viewModel. UpdateTitlePost (” Kotlin “) started the second request, did not perform to the mPostValueRunnable,

Since carried out the viewModel. UpdateTitlePost (” Kotlin “) at the request of postValue incoming parameters namely “Kotlin”, we take a look at the second request breakpoint value:

PostTask = mPendingData == NOT_SET (” Kotlin “); It then assigns the latest value “Kotlin” to mPendingData, which, remember, is Kotlin. Finally, if postTask is false, it returns directly, that is, the second postValue does not go to postToMainThread(mPostValueRunnable) at all.

MPostValueRunnable is executed only after the second request postValue is executed,

At this point, the global variable mPendingData has changed to “Kotlin”, and setValue is executed in Runnable with the parameter “Kotlin”, which finally triggers the onChange callback of liveData, as proposed at the beginning of this article. You only get onChange once.

PostValue loses value postValue loses value

  • PostValue puts the callback logic into the Runnable and posts it to the Handler, which updates it on the main thread, so there is a time lag between postValue and Runnable execution.

  • Before Runnable is executed, the value of mPendingData in the first call is overwritten by the value of the second call, and the value of mPendingData can only be the latest one when Runnable is executed. That is, the onChange callback is triggered to the UI with the second value.

“Understand, understand, pay boss yyds”, xiao Wang comrade listened to my a cent (blow) analysis (skin) nodded.

If postValue is executed to Runnable, then why not execute Runnable twice?

PostTask = postTask (); postTask (); postTask (); postTask (); MPendingData == NOT_SET initial value, postTask is true, the parameter value is assigned to mPendingData, and a Runnable is posted.

PostTask is false, postTask is false, and no Runnable is executed. This is why there are no two Runnable executions.

In Runnable, mPendingData is reset to NOT_SET to receive a new round of execution logic.”

Xiao Wang kept it in mind silently.

PostValue loses the old value because the post Runnable is required. SetValue does not lose the old value because the post Runnable is required.

Yes, setValue does not lose value. This is also the difference between postValue and setValue. We can do that.

All else being equal, I’m going to change postValue to setValue,

You can see from the output that the activity values “Java” and “Kotlin” are received.

This verifies that setValue, unlike postValue, does not lose its value.

So, Xiao Wang comrade solved the bug happy to go, I also wear headphones, continue my code journey.

Recommended reading:

How does LiveData replace EventBus? Communication principle and viscous event analysis of LiveData