Android知识点:LiveData为啥连续postValue两次,第一次的值会丢失?
Posted River_ly
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android知识点:LiveData为啥连续postValue两次,第一次的值会丢失?相关的知识,希望对你有一定的参考价值。
作者:付十一
有一天,我正听着歌,开心的敲着代码,这时候小王同志急冲冲的跑过来拍了拍我的肩膀,“付老板,我这遇到一个问题,连续两次请求同一个接口,参数传的不同,但是livedata的onChange回调只走了一次,UI界面上只有一个地方更新成功了,这是咋回事啊?”
一听有bug,这我可就来劲了,立马摘下耳机,“来,上代码”, “诶~你这是在UI层注册了一个监听,然后在请求接口的地方,利用livedata连续postValue两次。看情况你这应该是postValue搞的鬼,不急,我来模拟下场景,看看他的内部实现”。
新建一个ViewModel,创建一个String类型值的MutableLiveData,向外抛出一个更新String值的方法,并且使用liveData的postValue来通知UI更新数据。
class MyViewModel : ViewModel()
val countLiveData = MutableLiveData<String>()
var title: String = ""
/**
* livedata postValue
*/
fun updateTitlePost(str:String)
title = str
countLiveData.postValue(title)
activity上直接注册一个Observer,并且在button的click中连续两次调用viewModel中更新String值的方法。
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")
)
/**
* 模拟请求,连续调用两次,第一次更新值为‘Java’,
* 第二次更新值为‘Kotlin’
*/
findViewById<Button>(R.id.button).setOnClickListener
viewModel.updateTitlePost("Java")
viewModel.updateTitlePost("Kotlin")
点击模拟接口请求,连续调用两次,第一次更新值为‘Java’,第二次更新值为‘Kotlin’。运行看看。
可以看到控制台的log输出,不管点几次,只有Kotlin的值更新成功了,也就是符合小王同志的场景了,那第一次更新的Java值跑哪去了?
为什么连续postValue两次,第一次的值会丢失?
带着问题,我们来看看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);
诶~~ 值得注意的是,官方已经在postValue的注释上说明了。
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
如果在主线程执行一个已发布的任务之前多次调用此方法,则只会分派最后一个值。
虽然官方直接解释了,我们还是要搞清楚原理不是。
接着看源码,postValue内部维护了一个boolean类型的postTask,利用synchronized对一个objec对象 mDataLock加了锁,并且内部有一个全局变量mPendingData,这是问题的关键。每次postValue会将新的值赋给mPendingData,然后会在一个Runnable中进行值的分发,且使用ArchTaskExecutor将该Runnable的任务发布到主线程中(题外话:这就是为什么postValue一直在主线程更新的原因)。
我通过断点的方式,跟踪了一下。
viewModel.updateTitlePost("Java")
viewModel.updateTitlePost("Kotlin")
这两步的请求路线。第一次调用viewModel.updateTitlePost(“Java”),postValue内部值如下图:
传入的value值为“Java”,mPendingData等于初始值,postStask则为true,则直接走到了ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable)方法。
记住这个时候mPendingData的值为“Java”,我继续利用断点执行下一步,却发现直接执行了viewModel.updateTitlePost(“Kotlin”)开始了第二次的请求,并没有执行到mPostValueRunnable中,既然执行了viewModel.updateTitlePost(“Kotlin”)请求,postValue传入的参数也就是“Kotlin”,我们再来看看第二次请求的断点值:
value值为“Kotlin”,因为第一次postValue时,全局变量mPendingData值为“Java”,所以这次postTask = mPendingData == NOT_SET所执行后的值为false,接着将最新的值“Kotlin”赋值给了mPendingData,记住,此时的mPendingData值为Kotlin。最后,如果postTask为false,则直接return了,也就是说第二次的postValue压根不会再去 postToMainThread(mPostValueRunnable)。
当第二次请求postValue执行后,mPostValueRunnable才开始执行。
这个时候全局变量mPendingData的值已经变为“Kotlin”了,直接携带“Kotlin”这个参数在Runnable中 执行setValue,也就是最后触发了liveData的onChange回调,也就是文章一开始提出的,只会收到一次onChange。
至此,postValue为什么会丢失值的问题已经分析结束了,那我们再来捋一捋。
- postValue方法内部其实是将值得回调逻辑放到了Runnable中,再post给Handler,利用Handler在主线程中更新,因此从postValue到执行Runnable,中间是存在时间差的。
- 在执行Runnable之前,因为是连续调用的postValue,第一次mPendingData的值被第二次调用时的值覆盖掉了,最后执行Runnable时,mPendingData的值只能是最新的那个,也就是带着第二次的值触发onChange回调给UI。
“懂了,懂了,付老板yyds”,小王同志听了我的一番分(吹)析(皮)点了点头。
“那还有一个问题,既然postValue最后会执行到Runnable,那连续两次postValue,为什么没有两次Runnable的执行呢?”
“其实这个问题在上面分析中已经给出了答案,在postValue函数内部维护了一个boolean类型的postTask,而postTask是true还是false是由mPendingData值来决定的,第一次执行postValue,mPendingData == NOT_SET初始值,postTask则为true,接着将参数值赋值给了mPendingData,然后开始post了一个Runnable。
这个时候开始了第二次的postValue,mPendingData由于不再等于初始值NOT_SET,则postTask为false,postTask为false,那就直接return了,就不会再执行一个Runnable了,这就是为什么没有两次Runnable的执行。
而在Runnable中,mPendingData会被重置为NOT_SET,开始接受新一轮的执行逻辑。”
小王默默的记在了心里。
“那既然postValue丢失旧值是因为需要post Runnable的缘故,那setValue没有这一步,是不是就不会丢失值了。”
“哟~~ 不错嘛,学会举一反三了,没错的,setValue不会丢值,这也是postValue和setValue的不同之处。我们可以实践看看。
其他不变,将postValue改为setValue。
从输出上可以看到activity中“Java”和“Kotlin”的值都收到了。”
这也就验证了setValue和postValue不一样,并不会丢失值。
于是,小王同志解决了bug开开心心的走了,我也戴上耳机,继续我的代码之旅。
前段时间还收集整理了android高工必备技能知识脑图和核心知识点笔记文档!既能够夯实底层原理核心技术点,又能够掌握普通开发者,难以触及的架构设计方法论。那你在工作中、团队里、面试时,也就拥有了同行难以复制的核心竞争力。
相关的一些知识点解析都已经做了收录整理上传至公号中:Android开发之家,大家可以自行访问查阅。
以上是关于Android知识点:LiveData为啥连续postValue两次,第一次的值会丢失?的主要内容,如果未能解决你的问题,请参考以下文章
谁能取代Android的LiveData- StateFlow or SharedFlow?
为啥 LiveData 有一个单独的 MutableLiveData 子类?
为啥观察 LiveData 时不调用 onChanged()
谁能取代Android的LiveData- StateFlow or SharedFlow?
为啥用 viewLifecycleOwner 观察到的 LiveData 在 onDestroyView 之后会得到回调?