LiveData数据倒灌?你真的用对了吗?源码解析

文章目录

  • livedata/lifecycle
    • androidx-lifecycle
      • 基本使用
      • uml-关键角色关系
      • 观察者模式
      • 关键源码分析
    • livedata
      • 基本使用
      • 源码分析
        • 还是从observe开始
        • livedata变化通知观察者
    • viewmodel源码分析
  • FAQ
    • lifecycle用到的设计模式
    • lifecycle是如何监听aty生命周期的
    • 一些废弃和原因
      • 为什么废弃注解形式的event
      • 为什么不再支持onRestart
    • livedata是如何和lifecycle绑定的
    • 设置相同的值,订阅的观察者们会收到同样的值吗
    • 哪些情况才会分发值通知更新
    • livedata是如何感知生命周期的,需要取消注册吗?
    • setValue 和 postValue 有什么区别
    • 非激活态会收到事件吗,激活后最多收到几次事件?
    • 粘性事件和数据倒灌
      • 粘性事件
      • 数据倒灌
      • 如何解决数据倒灌
      • 得出livedata适用场景
    • observeForever有啥用
    • livedata作为fragment之间通信

livedata/lifecycle

在看livedata之前,还得先看看AndroidX自带的lifecycle,这也是livedata感知UI容器生命周期的保证。

androidx-lifecycle

基本使用

定义一个lifecycle watcher,用于观察activity的生命周期:

class LifecycleWatcher : LifecycleEventObserver, DefaultLifecycleObserver, LifecycleObserver {override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {Lifecycle.Event.ON_RESUMELog.e(TAG, lifecycleOwner.javaClass.name + " = " + event.toString())}override fun onCreate(owner: LifecycleOwner) {super.onCreate(owner)Log.e(TAG, "onCreate")}override fun onStart(owner: LifecycleOwner) {super.onStart(owner)Log.e(TAG, "onStart")}
}

然后将这个watcher与对应的activity关联起来形成观察者-被观察者联系:

    override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)...lifecycle.addObserver(LifecycleWatcher())...}

这样咱们就可以在logcat中看到相关的打印信息了。

需要注意的是,因为onRestart生命周期因为会产生歧义或使用场景有限,就不支持了,这在文末会有说明。

uml-关键角色关系

AndroidX_Lifecycle_uml

观察者模式

androidx的activity种有一个get方法:getLifecycle获取到当前activity的生命周期。

然后lifecycle.addObserver(watcher)

所以,这是一个观察者模式,其中主要四个角色:

  • activity作为lifeCycleOwner,是被观察者持有者(生命周期持有者)。
  • 而activity里面的lifecycle才是被观察者
  • addObserver方法的参数是观察者,如presenter、viewmodel等。
  • LifecycleRegistry作为纽带,连接观察者和被观察者。

关键源码分析

  1. 简单使用:

    我们拿到MainActivty的onCreate方法中,拿到它的生命周期(被观察者)并添加一个观察者实现:

    class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)...lifecycle.addObserver(LifecycleWatcher())}
    

    观察者实现,实现于LifecycleEventObserver或DefaultLifecycleObserver或LifecycleObserver(注解方式已废弃)。

    其中LifecycleEventObserver为event形式观察,DefaultLifecycleObserver为callback形式观察全生命周期。

    可以看到,它的父类是:public interface DefaultLifecycleObserver extends FullLifecycleObserver

    class LifecycleWatcher : LifecycleEventObserver, DefaultLifecycleObserver, LifecycleObserver {override fun onStateChanged(lifecycleOwner: LifecycleOwner, event: Lifecycle.Event) {Lifecycle.Event.ON_RESUMELog.e(TAG, lifecycleOwner.javaClass.name + " = " + event.toString())}override fun onCreate(owner: LifecycleOwner) {super.onCreate(owner)Log.e(TAG, "onCreate")}override fun onStart(owner: LifecycleOwner) {super.onStart(owner)Log.e(TAG, "onStart")}...
    }
    

    这样我们就可以通过自定义的观察者来观察MainActivity的lifecycle了。

    我们使用起来非常简单,那它内部是怎么做到的呢?

  2. 从addObserver()开始

    addObserver的具体实现是LifecycleRegistry类:

    @Override
    public void addObserver(@NonNull LifecycleObserver observer) {State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;// 以状态模式对activity生命周期进行封装ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);// 观察者集合中没有当前观察者,就将当前观察者加入到集合中去ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);...// 同步当前生命周期的状态
    }
    

    可以看到,这个方法有两个要点:

    1. 状态模式对activity生命周期进行封装:
      static class ObserverWithState {State mState;LifecycleEventObserver mLifecycleObserver;ObserverWithState(LifecycleObserver observer, State initialState) {mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);mState = initialState;}void dispatchEvent(LifecycleOwner owner, Event event) {State newState = event.getTargetState();mState = min(mState, newState);mLifecycleObserver.onStateChanged(owner, event);mState = newState;}
      }
      
      这是LifecycleRegistry中的一个静态内部类,它不仅封装了activity的状态,LifecycleRegistry也是通过它下发状态变更的事件给观察者的,它是纽带中的纽带
    2. 观察者集合:
      FastSafeIterableMap<LifecycleObserver, ObserverWithState> mObserverMap
      观察者为键、当前状态为值的map集合。
  3. 如何观察activity生命周期改变并通知观察者的?

    下面来到了关键部分,我们再分析一下RegistryLifecycle的具体实现发现:

    handleLifecycleEvent这个public方法被触发后会执行一系列私有方法来同步观察者集合中的生命周期状态。

    handleLifecycleEvent -> _moveToState -> _sync() -> _forwardPass() or _backwardPass()下划线开头的方法都是私有的。

    通过枚举状态定义的顺序进行比较来向前或向后更新生命周期状态。

    再看这个方法的名字,handleLifecycleEvent,明显就是来处理生命周期事件的,显然它就是activity生命周期变动的回调处理。

    我们通过getLifecycle()可以直接获取MainActivity的生命周期,点进去发现是在ComponentActivty中实现了LifecycleOwner:

    public interface LifecycleOwner {/*** @return The lifecycle of the provider.*/@NonNullLifecycle getLifecycle();
    }
    

    并且也找到了ComponentActivity的实现和成员变量mLifecycleRegistry(纽带):

    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
    ...
    @Override
    public Lifecycle getLifecycle() {return mLifecycleRegistry;
    }
    

    一切都如预期般衔接上了。但随之而来的是噩梦。

    ComponentActivity中mLifecycleRegistry竟然只有getLifecycle()这个方法引用了,那ComponentActivity是如何把生命周期变动给到这个纽带的呢?

    我又去ComponentActivity的生命周期相关方法中挨个查看,发现都没有。。

    甚至于找到了隔壁FragmentActivity的LifecycleRegistry实现,但二者纽带的引用地址、变量名都不一样,肯定不是成员变量的覆写,说明ComponentActivity生命周期通知给纽带的逻辑在别处。

    于是我查了下资料,发现ComponentActivity的onCreate()方法中注册了一个ReportFragment

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {...super.onCreate(savedInstanceState);// 注册了个用于报告的空FragmentReportFragment.injectIfNeededIn(this);...
    }
    

    看这个名字起的很有意思,那究竟是要report什么呢?点进去看一看:

    public static void injectIfNeededIn(Activity activity) {if (Build.VERSION.SDK_INT >= 29) {// On API 29+, we can register for the correct Lifecycle callbacks directlyLifecycleCallbacks.registerIn(activity);}// Prior to API 29 and to maintain compatibility with older versions of// ProcessLifecycleOwner (which may not be updated when lifecycle-runtime is updated and// need to support activities that don't extend from FragmentActivity from support lib),// use a framework fragment to get the correct timing of Lifecycle eventsandroid.app.FragmentManager manager = activity.getFragmentManager();if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();// Hopefully, we are the first to make a transaction.manager.executePendingTransactions();}
    }
    

    映入眼帘的全是lifecycle相关,原来逻辑在这里!专门用来报告生命周期状态的空fragment

    • 可以看到SDK>=29时可以直接注册,activity.registerActivityLifecycleCallbacksactivity直接支持生命周期的回调。

    • 其他情况需要持有一个空ReportFragment来进行fragment生命周期的手动监听:

      @Override
      public void onStart() {super.onStart();dispatchStart(mProcessListener);dispatch(Lifecycle.Event.ON_START);
      }@Override
      public void onResume() {super.onResume();dispatchResume(mProcessListener);dispatch(Lifecycle.Event.ON_RESUME);
      }@Override
      public void onPause() {super.onPause();dispatch(Lifecycle.Event.ON_PAUSE);
      }
      ...
      

    最终二者都是通过一个static方法进行分发的:

    static void dispatch(@NonNull Activity activity, @NonNull Lifecycle.Event event) {if (activity instanceof LifecycleRegistryOwner) {((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);return;}if (activity instanceof LifecycleOwner) {Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();if (lifecycle instanceof LifecycleRegistry) {((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);}}
    }
    

    至此,终于找到了handleLifecycleEvent调用的地方,当纽带收到这个event后,经过一系列私有方法处理成state,下发给我们定义的观察者,这样就完成了观察者模式。

  4. 状态的封装

    public State getTargetState() {switch (this) {case ON_CREATE:case ON_STOP:return State.CREATED;case ON_START:case ON_PAUSE:return State.STARTED;case ON_RESUME:return State.RESUMED;case ON_DESTROY:return State.DESTROYED;case ON_ANY:break;}
    }
    

    这样就完成了lifecycle到state的映射。

livedata

基本使用

基本使用,定义一个viewmodel,view model中有一个String类型的livedata:

class SimpleViewModel : ViewModel() {private val text: MutableLiveData<String> = MutableLiveData()init {text.value = "Hello, World!"}fun getText(): LiveData<String> {return text}fun changeText(newText: String) {text.value = newText}
}

初始化viewmodel,并在ui中监听livedata的String内容变化:

    private fun initViewModel() {binding.buttonChangeText.setOnClickListener {mViewModel?.changeText("new text")}mViewModel = ViewModelProvider(this)[SimpleViewModel::class.java]mViewModel?.getText()?.observe(this, Observer {// val newValue = it + timeOfChangeval newValue = ittimeOfChange++binding.tvText.text = newValue})}

以上就是livedata的基本使用了,当点击按钮的时候会触发livedata的value改变,然后通知观察者响应。

源码分析

通过基本使用,我们发现不论是viewmodel还是livedata,都把lifecycle owner传进去了。

而lifecycle owner一般是由activity或fragment实现的,所以通过这个传递不难猜出,是通过lifecycle owner建立的生命周期感知。

还是从observe开始

从基本使用上来看,livedata也是基于观察者模式,通过观察livedata的value值改变而做出响应。

既然是观察者模式,我们就直接进入livedata的observe方法看看,它是如何实现观察者的监听的,不出意外,所有观察者会被放进一个list或者map中去,以便被观察者改变后对它进行回调。

    @MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {...// 这里是个封装类,封装并绑定了两个观察者,生命周期观察者和livedata的观察者。LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);// 这里就是所有观察者的集合了ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);...// 观察生命周期owner.getLifecycle().addObserver(wrapper);}

可以看到,关键逻辑就这三行。

观察者集合是以livedata的观察者接口为键,LifecycleBoundObserver这个封装类为值的集合。

前面lifecycle我们知道也是基于观察者模式,而LifecycleBoundObserver这个类的名字就极其明显:是将生命周期和livedata的观察者(接口)进行绑定,进入这个类看看:

// 实现了LifecycleEventObserver,观察lifecycle owner,并且继承ObserverWrapper
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {// 枚举值大于等于start表示可激活,即start || resume状态return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}// 对lifecycle owner的监听响应@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();if (currentState == DESTROYED) {// destroy状态直接移除观察者// 这个mObserver是来自父类,父类是对livedata观察者回调的包装removeObserver(mObserver);return;}Lifecycle.State prevState = null;// 更新当前状态,并根据状态判断是否分发valuewhile (prevState != currentState) {prevState = currentState;// 这行代码是关键,对生命周期变化的处理activeStateChanged(shouldBeActive());currentState = mOwner.getLifecycle().getCurrentState();}}...@Overrideboolean shouldBeActive() {// 这里可以看到枚举值 >= STARTED是激活态,枚举的定义顺序是:// DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED// 所以STARTED||RESUMED就表示activity是激活态return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}...public boolean isAtLeast(@NonNull State state) {// 比较,大于等于即可STARTED || RESUMEDreturn compareTo(state) >= 0;}
}

可以看到,LifecycleBoundObserver不仅包装了livedata的观察者,还包装了对生命周期的观察者,承担着两个观察者的作用,并将二者绑定:如果是lifecycle是destroy状态就不再观察livedata,如果生命周期发生改变,且由非激活状态变为激活状态:activeStateChanged(shouldBeActive());

    void activeStateChanged(boolean newActive) {if (newActive == mActive) {return;}// immediately set active state, so we'd never dispatch anything to inactive// ownermActive = newActive;changeActiveCounter(mActive ? 1 : -1);if (mActive) {// 可以看到,这里如果是激活态,会进行了value的分发,经过上面过滤,必须是由非激活态转为激活态才会分发,这样的场景就是后台更新操作dispatchingValue(this);}}

通过观察源码我们发现,假如我们点击按钮触发livedata的更新操作,但是延迟3秒才postValue,或者runOnUiThread的setValue,但三秒还没到,我们就打开了另一个activity,使得当前activity进入非激活态。

而非激活态是不会将变化通知给观察者的,只有当状态是非激活太变为激活态(与上一个状态不同&&当前状态是激活态&&livedata版本大于原版本发生了变化),才会通知观察者。

这样就将生命周期和livedata绑定了起来,实现了只在激活状态下才分发value的更新。

下面再看看value的变化是如何分发给观察者的。

livedata变化通知观察者

上面我们看到了lifecycle观察者和livedata观察者进行了绑定,但是还是不知道livedata的内容发生改变后,是如何通知多有livedata的观察者的。

找到livedata的setValue()方法,进去一看便知:

/*** Sets the value. If there are active observers, the value will be dispatched to them.* <p>* This method must be called from the main thread. If you need set a value from a background* thread, you can use {@link #postValue(Object)}** @param value The new value*/
@MainThread
protected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);
}

先看看注释:

  1. 设置一个新的value,这回通知多有已激活的观察者,并将新值分发给他们。
  2. 这个方法必须在主线程被调用,子线程调用用postValue

再看看里面的逻辑:

  • 版本号加1
  • 更新value值
  • 分发

首先是版本号,初始值是-1,在livedata的构造函数中,如果没有给初始值,默认就用-1,如果给了初始值,则初始版本为-1 + 1 = 0,具体可以看源码,很清楚明白。

将value值更新无需多言,我们直接看分发value的逻辑:

    void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;// 是否是初始化操作if (initiator != null) {considerNotify(initiator);initiator = null;} else {// 不是初始化操作,是setValue操作,遍历观察者集合进行通知for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;}

可以看到,通知的时候大致分两种情况,是否是生命周期发生变化,还是setValue进行的通知,主要区别在于initiator观察者是否为null,不为null直接通知该观察者,为null遍历通知所有观察者

但他们最终都调用了considerNotify,这个方法才是通知逻辑,通知某个具体的观察者:

    private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData);}

livedata的版本号当前观察者的版本号进行对比,如果版本号大于原版本号则执行观察者的onChange回调方法,这样就通知了观察者。

所以observer.mLastVersion每一个观察者都有一个版本号需要和livedata的版本号进行同步。

注意这两个版本号的定义位置是不同的,当前观察者的版本号定义的位置有个坑会导致数据倒灌,后面会细说。

再来看看最终的时序图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sviTsZOl-1681211413877)(https://img.yameko.top/livedata%E6%97%B6%E5%BA%8F%E5%9B%BE.png)]

viewmodel源码分析

FAQ

参考:

  • https://juejin.cn/post/6991168529454088228#heading-4
  • https://juejin.cn/post/6991497263457501221
  • https://xiaozhuanlan.com/topic/6719328450

lifecycle用到的设计模式

  • 观察者模式
  • 状态模式

lifecycle是如何监听aty生命周期的

在ComponentActivity中使用一个空的ReportFragment进行监听,然后通过LifecycleRegestry作为纽带回调给观察者。

一些废弃和原因

为什么废弃注解形式的event

提问chatGPT:

问:https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=zh-cn#2.4.0

这篇文章中,为什么要废弃@OnLifecycleEvent

答:AndroidX Lifecycle 2.4.0 版本废弃了 @OnLifecycleEvent 注解,主要原因是该注解具有以下问题:

  1. 该注解容易与混淆和代码可读性相关的其他注解混淆,导致使用者难以理解。
  2. 该注解的使用场景较为有限,通常需要在 Activity 或 Fragment 中使用,而其他组件则很难使用。
  3. 该注解需要使用者手动处理生命周期事件,导致使用者难以进行统一管理和维护。

因此,为了提高代码的可读性、灵活性和易维护性,AndroidX Lifecycle 废弃了该注解,并推荐使用 LifecycleEventObserver 和 DefaultLifecycleObserver 接口来监听生命周期事件。

为什么不再支持onRestart

chatGPT:

根据我们之前的讨论,主要有两个可能的原因导致 AndroidX Lifecycle 不再支持 onRestart 方法:

  1. onRestart 方法的语义模糊,不太容易理解。这也是 AndroidX 官方文档中提到的原因。由于 onRestart 的执行时机比较特殊,它会在 Activity 从后台返回到前台时执行。但是它并不会在 Activity 首次启动时执行,这就容易让人感到困惑,不太好理解。
  2. onRestart 方法的使用场景比较有限。由于 onRestart 方法只有在 Activity 从后台返回到前台时才会执行,而这种情况并不是非常常见,因此 onRestart 方法的使用场景比较有限。考虑到 AndroidX Lifecycle 的设计目标是提供一种更加简单、可靠的方式来管理 Activity 和 Fragment 的生命周期,因此它们决定不再支持 onRestart 方法,以便更好地满足设计目标。

综上所述,我们可以得出结论:AndroidX Lifecycle 不再支持 onRestart 方法的主要原因是它的语义模糊,使用场景比较有限,同时为了更好地满足设计目标,AndroidX Lifecycle 决定放弃对该方法的支持。

总结:

  • 语意模糊冗余,与onResume语义重叠,容易混淆、可读性差。
  • 使用场景有限,不经常使用且可替代
  • Google官方可能有意简化生命周期流程

livedata是如何和lifecycle绑定的

lifecycle的监听是观察者模式,前面讲过,livedata的也是观察者模式。

通过LifecycleBoundObserver类,将生命周期的观察者和livedata的观察者绑定封装在了一起。

激活状态 = start || resume

通过监听lifecycle,当生命周期发生变化时开始过滤:

  • 只有当生命周期状态发生改变 &&
  • 当前状态为激活状态 &&
  • livedata的version大于上一次的(值发生改变)

同时满足上面三种情况,则生命周期的变化会通知观察者。

如果一直是激活态,只要value值更新,哪怕更新的值和原来的值一样,版本号也会+1,只要和原版本不同就会通知观察者(即使新值和旧值是一样的)。

设置相同的值,订阅的观察者们会收到同样的值吗

会,只和版本号有关

哪些情况才会分发值通知更新

  1. 调用 setValue 和 postValue 并且 LifecycleOwner 处于活跃状态时,其实这种情况就是第三种情况
  2. LiveData 有值,并且处于活跃状态时,调用 LiveData#observe 订阅观察者
  3. LiveData 有新值,也就是 ObserverWrapper 的 mLastVersion 小于 LiveData 的 mVersion,LifecycleOwner 从不活跃状态转为活跃状态时

作者:那根笔
链接:https://juejin.cn/post/6991497263457501221
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

livedata是如何感知生命周期的,需要取消注册吗?

在调用livedata的observe方法时,会调用 owner.getLifecycle().addObserver 已达到感知生命周期的目的。

而在LifecycleBoundObserver的生命周期回调方法onStateChanged中如果是destroy状态会移除观察者,所以不需要我们手动取消注册,是自动感知的。

setValue 和 postValue 有什么区别

    private final Runnable mPostValueRunnable = new Runnable() {@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}//noinspection uncheckedsetValue((T) newValue);}};...protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}

在子线程中调用setValue会抛出异常,不允许在子线程中调用该方法:throw new IllegalStateException("Cannot invoke " + methodName + " on a background" + " thread");

再看postValue源码,发现postValue最终还是通过handler跳回主线程调用setValue.

但是由于是允许在子线程调用,需要考虑多线程安全问题,所以在设置新值的过程pendingData或者newData都进行了synchronized加锁处理。

非激活态会收到事件吗,激活后最多收到几次事件?

GPT:

不会收到,当 Activity 不处于活动状态时,LiveData会将数据更改缓存起来,等到 Activity再次处于活动状态时,会将缓存的最新数据发送给观察者,只会通知一次

这种方式可以避免数据更新过程中多次通知观察者导致性能问题。

粘性事件和数据倒灌

粘性事件

先说下什么是粘性事件:当livedata在观察者开始观察之前就发生改变,当观察者开始观察后,这个改变仍然会通知到观察者,这就是粘性事件。

那么livedata是如何实现粘性事件的呢?

可以看到前面livedata源码的分析,通知观察者的情况一种可能是:activity生命状态发生改变&&由非激活态转为激活态&&版本号更新了。

那么再看这样一种场景:在activity初始化viewModel和livedata后,这时还没有调用observe开始观察livedata,我们先对livedata调用setValue,然后三秒后再观察livedata,发现仍然可以收到通知。

这样就复现了一个粘性事件。

其最根本的原因是,lastState默认初始化为null,这样lastActive就默认是false,而onsStart后,当前状态变为激活态,currentActive为true,与之前状态一比较发现生命周期发生改变,于是下发通知,这时又发现当前livedata版本号也比观察者的版本号(默认初始值为-1)大,刚好就会下发成功,则粘性事件到达。

数据倒灌

参考:

  • https://www.jianshu.com/p/cb6a80865805

首先先明确这样一个定义:

被观察者数据发生变化,如何确定这次变化需要通知到观察者?在livedata源码中,有俩版本号用作该控制。

第一个版本号是livedata的版本号,属于被观察者的,第二个版本号是该livedata的每一个观察者都有一个lastVersion,每次分发成功后会同步这两个版本号的值,将每个观察者的lastVersion更新位livedata的版本号。

基于上面livedata的粘性事件,通知观察者的情况一种可能是:activity生命状态发生改变&&由非激活态转为激活态&&版本号更新了。

这种情况会通知观察者。

那么再看现实中的一个场景:购买成功跳评论页。

购买成功用一个bool的livedata来存储分发,在主页购买成功后,livedata被setValue为true然后通知每一位观察者。

观察者收到通知后打开评论页,正常流程就是这么简单,似乎没有什么问题吧。

  1. 但是,假设现在我们已经购买成功且去到评论页,然后再返回到主页。
  2. 这时突然屏幕旋转,activity被迫重建但viewmodel没有销毁,livedata存储的也是上次的值购买成功true。
  3. 这时在onCreate方法中进行对livedata的observe()观察,突然发现收到一个事件,这个事件被消费过已经打开了一次评论页,现在又会再打开一次,情况变得扑朔迷离了起来。

这是为什么呢?

答案就在observe()方法里,会创建一个新的LifecycleBoundObserver观察者,但创建新的观察者意味着版本号默认初始化为-1,且由于生命周期发生变动,由onCreate -> onStart转为激活态,刚好满足分发事件给这个新观察者的条件,那livedata就把它目前存储的最新值分发出去,所以还会再打开评论页。

如何解决数据倒灌

  • 解决方案参考:https://juejin.cn/post/7148049769057746952#heading-6

  • 反射侵入:重写livedata,在observe()中反射同步两个版本号,将这个观察者的版本号在初始化的时候同步为livedata的版本号。这样的话事件就是非粘性事件了。

  • 使用google大神的SingleLiveEvent包装类:仍然是粘性事件,只是粘性事件没有分发,但可解决数据倒灌,但是生命周期变化的事件只消费一次,setValue引起的事件分发才会被消费多次,具体实现通过在onChange回调中先套一层原子类+CAS

  • 使用事件包装器:在观察的model中除了content,再加一个isHandled属性,默认为false,第一次得到该数据内容后将isHandled设置为true,这样每次setValue,set的是包装后的event model,与SingleLiveEvent包装类有异曲同工之妙,只是一个在业务层一个更接近于框架层。但仍然是粘性事件,只是解决了数据倒灌。

  • UnPeekLiveData:KunMinX大神实现的livedata覆写类,其实原理和google大神的SingleLiveEvent有异曲同工之妙,都是observe()方法阻塞,setValue()再放开。它是通过map存储每一个观察者以其内存地址生产的唯一ID作为键,是否消费bool类型作为值,在setValue和observe方法中控制。仍然是粘性事件,只是在回调给开发者的onChange方法包了一层过滤掉了而已。

所以,只要没有入侵更改版本号的,基本上都还是粘性事件,只是通过某种手段进行过滤。

得出livedata适用场景

那么为什么会出现数据倒灌,我们的使用场景是否正确?

livedata其实是不适合处理业务逻辑的,因为它的观察者模式并非单一仅仅受到被观察者影响,同时也会受到activity的生命周期影响。

所以如果处理后台业务逻辑时,我们就需要考虑如果被当前activity的生命周期限制依然能正常工作,并保证结果一致性,这样才能放心使用。

那么哪种情况刚好可以放心使用呢?

UI的更新,activity作为UI的容器,viewmodel、livedata作为感知activity并提供数据,可以将数据缓存在内存中,以便activity激活时快速响应

UI的一致性比较容易得到保证,即使得到的是上一次最新的数据,虽然对于现在来说可能是旧数据,但总比没有好,而且我们在onCreate中可以重新进行远端请求并通过setValue更新livedata,进而更新界面。

所以得出结论:livedata的适用场景是UI变化的响应式编程,而不是业务逻辑上变化的观察和响应,否则业务逻辑的结果一致性受到activity生命周期影响而无法保证。

如果是业务侧的观察者模式可以考虑其他观察者比如:observeForever或者RX、Bus等。

observeForever有啥用

AlwaysActiveObserver与生命周期无感知

如果你想无论页面处于何种生命周期,setValue/postValue之后立刻回到数据。那么可以使用observerForever()方法,使用起来与observer()没有太大差别. 因为AlwaysActiveObserver没有实现GenericLifecycleObserver 接口,不能感应生命周期。

但是需要注意的是,在用完之后,一定要记得在onDestroy()方法中调用removeObserver()方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,会造成内存泄露。

用它可以实现一个事件总线来代替event bus。

livedata作为fragment之间通信

我们已经知道,ViewModel能够将数据从Activity中剥离出来。只要Activity不被销毁,ViewModel会一直存储,并且独立于Activity的配置变化。

Fragment可以被看作Activty的子页面,即一个Activity中可以包含多个Fragment.这些Fragment彼此独立,但是又都属于同一个Activity.

基于ViewModel和Fragment组件的这些特性,我们可以利用LiveData,实现同一个Activity中的不同Fragment间的通信,因为不同的Fragment得到的都是同一个LiveData;

参考:https://blog.csdn.net/qq_43404873/article/details/109556209

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/61147.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 优雅实现多租户架构,so easy~!

点击关注公众号&#xff0c;Java干货及时送达 国内最强微服务框架&#xff0c;没有之一&#xff01; 几乎覆盖 Spring Boot 所有操作&#xff01; 一、概述 1.什么是多租户架构&#xff1f; 多租户架构是指在一个应用中支持多个租户&#xff08;Tenant&#xff09;同时访问&…

Jenkins + Docker 一键自动化部署 Spring Boot 项目,步骤齐全,少走坑路!

点击关注公众号&#xff0c;Java干货及时送达 国内最强微服务框架&#xff0c;没有之一&#xff01; 几乎覆盖 Spring Boot 所有操作&#xff01; 本文章实现最简单全面的 Jenkinsdockerspringboot 一键自动部署项目&#xff0c;步骤齐全&#xff0c;少走坑路。 环境&#xff1…

MyBatis-Plus 可视化代码生成器来啦,让你的开发效率大大提速!!

点击关注公众号&#xff0c;Java干货及时送达 学习 Spring Cloud 微服务的正确姿势&#xff01; 用上 ChatGPT 啦&#xff0c;强的离谱&#xff01; 终于把 Spring Boot 3.0 写成书了&#xff01; 前言 在基于Mybatis的开发模式中&#xff0c;很多开发者还会选择Mybatis-Plus来…

农民工与学生为楼癫狂 富人加速撤离

首先是各级政府不约而同开始鼓励农民工进城买房。然后&#xff0c;国家信息中心宏观经济形势课题组的报告&#xff0c;甚至把这件事提高到了生死攸关的高度&#xff1a;农民工市民化&#xff0c;是消化住宅库存的关键环节和成败所在。特别想请教国家信息中心宏观经济形势课题组…

计算机成下一个土木了??

来源&#xff1a;知乎 最近互联网裁员&#xff0c;有网友热议&#xff1a;2022年大规模裁员后&#xff0c;计算机专业会不会成为下一个土木&#xff1f; 前些年抓住了互联网行业的红利期&#xff0c;进入大厂的员工&#xff0c;基本可以实现在一线城市买房扎根。 但反观现在&am…

读书笔记-大颠狂(非同寻常的大众幻想与群众性癫狂)

本书内容概要 《财富》杂志鼎力推荐的75本商务必读书之一&#xff0c;《金融时报》评选的史上最佳10部金融作品之一。本书在金融史上的地位不言自喻&#xff0c;在世界各地流传了近200年依然畅销不衰。它不单是一本金融投资领域的超级经典&#xff0c;也是一部有关人类愚行的编…

让人癫狂的24号,请你慢点离开

那一年&#xff0c;你18岁&#xff0c;初进联盟&#xff0c;受世人怀疑&#xff0c;拿下扣篮冠军  那一年&#xff0c;你19岁&#xff0c;史上ALL STAR首发&#xff0c;惊艳世人  那一年&#xff0c;你20岁&#xff0c;坐稳球队先发&#xff0c;场均19&#xff0c;成为一股…

IntelliJ IDEA 2019,从入门到癫狂,图文教程

点击上方“小哈学Java”&#xff0c;选择“星标” 回复“666”&#xff0c;领取100G独家整理的学习资料哟~ 来源&#xff1a;http://t.cn/Eiv6CTU 如果说IntelliJ IDEA是一款现代化智能开发工具的话&#xff0c;Eclipse则称得上是石器时代的东西了。 其实笔者也是一枚从Eclipse…

IntelliJ IDEA 2019从入门到癫狂 图文教程!

点击上方“方志朋”&#xff0c;选择“设为星标” 回复”666“获取新整理的面试资料 作者&#xff1a;yizhiwazi 来源&#xff1a;www.jianshu.com/p/9c65b7613c30 前言&#xff1a;IntelliJ IDEA 如果说IntelliJ IDEA是一款现代化智能开发工具的话&#xff0c;Eclipse则称得上…

新世纪的群众性幻想与癫狂3

又泡沫的地方&#xff0c;就有财富的地方 自我意淫的人造牛市 进入2015年以来&#xff0c;静默已久的A股股价就出现了一个令人意想不到的暴涨。到2015年6月中旬&#xff0c;上证综指已较2014年7月上涨了152%、较年初上涨了59.7%&#xff0c;中小企业板、创业板较年初更是上涨了…

癫狂的dom——利用css3让dom动起来

不废话&#xff0c;先来看一下效果&#xff1a; 下面上代码&#xff1a; html部分&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title></title><link href"shake.css" …

IntelliJ IDEA 2019从入门到癫狂

前言&#xff1a;IntelliJ IDEA 如果说IntelliJ IDEA是一款现代化智能开发工具的话&#xff0c;Eclipse则称得上是石器时代的东西了。 其实笔者也是一枚从Eclipse转IDEA的探索者&#xff0c;随着近期的不断开发实践和调试&#xff0c;逐步体会到这款智能IDE带来的巨大开发便利…

新世纪的群众性幻想与癫狂

历史不会改变&#xff0c;只会一次次的重复 经过了千万年的进化&#xff0c;人类已经站在了食物链的顶端。人类的进化并不是肢体或皮毛上演化所带来的结果&#xff0c;而是人类具有智慧&#xff0c;知道使用大脑去思考判断。但是&#xff0c;人毕竟还是动物&#xff0c;内心所保…

IntelliJ IDEA 2019从入门到癫狂 图文教程

点击上方“朱小厮的博客”&#xff0c;选择“设为星标” 做积极的人&#xff0c;而不是积极废人 来源&#xff1a;http://t.cn/Eiv6CTU 前言&#xff1a;IntelliJ IDEA 如果说IntelliJ IDEA是一款现代化智能开发工具的话&#xff0c;Eclipse则称得上是石器时代的东西了。 其实笔…

CXO是什么?医药板块龙头都有哪些?

制药行业是当今最复杂、监管最严格的行业之一。新药的开发&#xff0c;从最初的发现到最终的批准&#xff0c;是一个漫长而昂贵的过程&#xff0c;需要许多不同专业人士的专业知识。而制药公司面临的主要挑战之一是平衡创新需求与成本效率需求&#xff0c;面对这个挑战&#xf…

医疗器械上市公司

内在上交所和深交所上市的公司中&#xff0c;主营业务为医疗器械的一共有35家&#xff0c;在港交所上市的有8家&#xff0c;共计43家。 图片来自“123RF图库” 医疗器械、药品和医疗服务并称为医疗行业的三驾马车。国家政策的支持&#xff0c;市场容量的扩张&#xff0c;无疑…

全网盘点有哪些免费开源提高生产力的 GitHub 项目火了?

全网盘点有哪些免费开源提高生产力的 GitHub 项目火了&#xff1f; 开箱即用的地理信息系统 「地理信息系统&#xff08;Geographic Information System&#xff0c;简称 GIS&#xff09;」是一种用于采集、存储、处理、分析、管理和展示地理信息的计算机系统。 简单来说&…

科普:生成式 AI 简介

What I cannot create, I do not understand. - Richard Feynman 前言 你左拥右抱着 Stable Diffusion 和 MidJourney 创造美轮美奂的图片。 你熟练使用着 ChatGPT 和 LLaMa 创造辞致雅赡的文字。 你来回切换着 MuseNet 和 MuseGAN 创造高山流水的音乐。 毋庸置疑&#xff0c;人…

生成式 AI 简介

What I cannot create, I do not understand. - Richard Feynman 前言 你左拥右抱着 Stable Diffusion 和 MidJourney 创造美轮美奂的图片。 你熟练使用着 ChatGPT 和 LLaMa 创造辞致雅赡的文字。 你来回切换着 MuseNet 和 MuseGAN 创造高山流水的音乐。 毋庸置疑&#xff0c;人…

如何编写AI艺术提示[示例+模板]

有没有觉得制作 AI 艺术提示需要秘密和魔法&#xff1f;当您的所有提示都失败时&#xff0c;所有这些创意专业人士如何在Midjourney和DALL-E等工具中创建令人惊叹的&#xff0c;令人兴奋的视觉效果&#xff1f; 现实情况是&#xff0c;创建很酷的AI艺术提示并不像看起来那么复…