LiveData的几宗罪?

5,403 阅读8分钟

上周五中午和群友在群里友好的技术交流(互怼),起因就是它在群里大肆贬低liveData,我实在看不下去就回了几句。想着之前怎么说也算是看过liveData的源码还怕说不过嘛,但是真到要说的时候,才发现有好多东西都已经忘记了(气得我午饭都吃的不香),就想着找个地方把学习过的东西记录一下,也可以加深一下印象。

用法

      在讲liveData存在的罪过之前,我们先来看一下liveData的用法,我们一般用liveData就是结合着viewModel用,但是在平时工作中或是在群里看水友发他们的用法的时候,经常会发现一些我个人觉得不是很恰当的用法,下面咱们看一下这些用法。

          在repository层我们用一个delay函数模拟数据的获取

      在viewModel层创建一个函数来来调用repository层的getData()函数并把结果赋值给liveData

    然后在activity层调用viewModel的getData(),并且观察userName。

    相信很多同学可能或多或少这么用过,那么我们看看问题出在哪里呢。

    首先我们明白,viewModel就是为了保存activity之类的因为配置变换呀,或者在后台系统意外杀死的时候activity中需要保存的数据,这和activity的savedInstanceState有点像。

     如果咱们在没配置android:configchanges的情况下,当手机配置变换,activity被销毁咱们重新执行onCreate,执行到mViewModel.userName.observe的时候,因为liveData的粘性,它会取出viewModel的旧值进行展示,然后再调用mViewModel.getData()去请求网络,请求结果又去回调,就算请求结果一致,因为liveData的数据抖动问题,又会重新回调Observer的onChanged方法。如果onChanged方法中是要给recyclerView的item赋值,又有图片加载的话,则会导致闪烁的问题。

       大家也可以看到,上面这种用法不仅仅使我们多网络加载一次,更可能会导致数据闪烁,对用户的体验是十分不好的。

那么正确用法是什么呢

我们可以在ViewModel的init中去请求数据,这样确保了当activity的配置变换,我们不会再重复请求网络数据,也不会存在数据抖动的问题。

或者我们也可以直接用liveDataScope去初始化userName这个属性。

也有做法在获取数据之前先判断数据存不存在

当然如果项目repository层返回flow的话,也可以用flow的扩展函数asLiveData转换为liveData赋值给userName属性,当然也不能单独在activity调用函数转换。

大家怎么用肯定就结合项目架构来选择。

liveData的用法也简单说了一下,那我们看看liveData的那些罪过吧。

LiveData罪一 :粘性,数据倒灌

首先我们存放在viewModel中的数据可以简单分为state数据和event数据,state数据是什么呢,就是需要展示在界面上的东西,比如说我们从后台获取到的list数据是要通过recyclerView展示的,这可以说是state数据。 那么什么是event数据呢?所谓event数据就是类似保存的数据是用来发射一个事件的,比如说网络失败errorMsg我们是要用来弹出一个吐司或者snackbar,或者比如说我们用ShareViewModel做页面间通信的时候,由A页面发出的数据改变的这个事件,这个改变的数据我们也可以视为event数据。

我们得明白liveData被设计出来就是为了恢复界面的state数据,但是如果我们简单的用liveData去保存界面的event数据呢

可以举个实际的例子,很多同学会把网络请求错误这个状态值存进liveData放在viewModel中,然后在activity observe这个状态liveData然后去toast或者说snackbar。

viewModel中:

activity中:

然后会造成什么后果呢?第一次进入该activity,可能由于网络不佳,数据加载失败,我们会弹出来一个toast。但是当该activity因配置变换或者被系统意外杀死的时候,然后重新onCreate,重新observe,因为viewModel中的errorData是有值的,然后其中的值就会倒灌,此时又会回调Observer的onChanged方法,又弹出toast,这可以算是一个程序的设计错误了。

造成原因?

我们先来看一下liveData.observe方法

在这里有一个很重要的类LifecycleBoundObserver,它继承于ObserverWrapper,然后又实现了LifecycleEventObserver。首先实现了LifecycleEventObserver,就说明其具备观察activity生命周期的能力,这也就是为什么说liveData具有观察生命周期的能力,我们再来看看ObserverWrapper这个类

mObserver就是我们调用liveData.observe传进去的Observer,这里保存下来方便后面回调Observer的onChanged方法。mActive就是说这个activity是否在前台,是否还活跃。如果我们用的liveData.observe方法,如果不活跃,则不回调onChanged,当然如果用的liveData.observeForever则不受这个限制。下面这个mLastVersion就是罪魁祸首。

首先,START_VERSION初始值为-1,而liveData也有一个私有属性是mVersion一开始的值也是等于START_VERSION。当我们每次setValue,或者postValue时都会使mVersion++,然后就会调用到这个方法

如果observer.mLastVersion < mVersion的时候,observer的onChanged就会被回调。这样好像就不难理解了,当我初次进入这个activity的时候errorData就setValue了一次,mVersion就加一等于0,然后因系统意外杀死重新observe的时候,因为重新observe对应的也是新的observerWrapper,那么这个mLastVersion就为-1,自然就满足条件observer.mLastVersion < mVersion了。

但是在这里又有个问题了,我这里也没有setValue,postValue呀,就算满足条件了,但是我没有执行这个considerNotify又有什么用呢。

但是大家还记得lifecycleBoundObserver是实现了LifecycleEventObserver的吗,我们来看一下它的onStateChanged

当生命周期发生变换时,onStateChanged会被回调,然后会执行activeStateChanged方法,最后也会执行到considerNotify方法的。这其中有些细节代码可以防止无效的回调之类的,可以细细看看学习思想。

怎么办?

如果对于单消费者的情况,我们可以采用google的SingleLiveEvent 但是singleLiveEvent并没有解决多消费者订阅的情况,因为消费完一次mPending就被设置为false,导致其他消费者是无法消费的,具体可看google demo

多消费者的解决方案可以在github搜一下KunMinX大佬的UnPeekLiveData

也有写同学通过反射去改version值,个人不是很建议,反射比较花时间,而且侵入性过强。

LiveData罪二:postValue导致的数据丢失

对于一些高频率的数据源,比如说做直播的应用,我们把用户加入直播间回调当做数据源,当高频率加入直播间,自然而然算高频率数据源,我们调用liveData.postValue的时候可能会发现Observer的onChanged的中可能会丢失一些数据,这个明显是不被允许的

造成原因?

我们来看一下liveData.postValue这个函数

可以看到其实是用一个mPendingData先去存储这个value值,再调用postToMainThread方法,这个方法其实就是把那个runnable通过handler post到主线程去执行,然后我们来看一下这个runnable是个啥玩意儿\

runnable其实就是调用setValue。

我们也知道handler.post(runnable)也是把这个runnable封装成一个message加入到主线程的messagequeue中,messagequeue底层又是一个链表,插入顺序只有message.when决定,你没有delay的话,就基本是先进链表先执行,更可怕的就是遇到系统的一些类似于performTravalsals这种runnable的话,它还要插入一个异步屏障,还得等他先执行完才行。也就是说从postToMainThread到真正执行setValue这段时间有点久,你可能在这段时间再次调用了postValue,mPendingData被再次赋值,自然之前的值就丢失了。

怎么办?

其实说到底就是说我们liveData没有一个缓冲就缓存被丢失掉的值,那我们换吗,换成rxjava的flowable,换成kotlin的flow,sharedFlow都是可以的。

这个的确真的是liveData的罪过了,但是说实话高频率数据源开发中也是很难遇到的,但是为了程序的健壮性,使用mvvm架构的时候,我们的确是应该在repository层返回flow,万不应该返回liveData,liveData在viewModel和activity酌情使用,对于那种请求一次,这个数据在ui层次展示出来基本不怎么变化了,我们在viewModel中使用liveData没啥毛病的,但是对于那种类似于直播人数这个高频率刷新的咱们最好还是用flow吧

LiveData罪三:数据抖动

什么是liveData的数据抖动呢,就是说liveData setValue不判断值是否与旧值相等,都会回调Observer的onChanged,但是stateFlow的话的确是会做这个判断吧。

但是这也算不上一种罪过,因为我们完全可以在外面封装一层判断一下,这个很好解决,或者说我们可以把这种特性当成数据回调的一个标识。

最后

技术更新很快,也不是说旧技术就是low的是不好的,我们应该根据业务去做相应的判断,我们怎么去用才是最优美的,而且其中的思想真的只有了解过后才知道是怎么回事,也只有阅读过后才知道这些缺点是怎么回事,我们在具体场景遇到了该怎么办。