【用法总结】LiveData组件要点
- 1、如何实现和生命周期的关联?
- 1.1 observe的实现逻辑:
- 1.2 观察者的装饰者:ObserverWrapper
- 1.3 观察者集合的存储:SafeIterableMap<Observer<? super T>, ObserverWrapper>,以obser为key,ObserverWrapper对value
- 2、onChange()执行时机
- 3、说LiveData会数据倒灌是这么回事?
- 3.1 本质
- 3.2 使用LiveData实现事件注册-分发逻辑的问题
- 3.3 短时间内连续执行多次postValue,只有最后一次更新的值会收到数据变化的onChanged回调
- 参考文章
1、如何实现和生命周期的关联?
调用observe()方法时,第一个参数传入LifecycleOwner对象,而LifecycleOwner能通过getLifecycle()方法获取到lifecycle对象,然后执行lifecycle.addObserver()添加LiveData中数据(mData)变化的观察者对象。
1.1 observe的实现逻辑:
@MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {if (owner.getLifecycle().getCurrentState() == DESTROYED) {return;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}owner.getLifecycle().addObserver(wrapper);}
如上图所示,是使用LiveData组件实现数据更新-订阅的开发模式的层级结构。
- mData:setValue()之后更新的数据
- mVersion:在每一次调用setValue()时会进行更新
- mPending:调用postValue()时先将新的值暂存到该变量中,然后将Runnable任务post()到Handler中后,再将mPendingData
中的值赋值给mData变量,然后就是走setValue()的流程了。
1.2 观察者的装饰者:ObserverWrapper
该类是Observer的包装类,是一个抽象类,具体实现类是LifecycleBoundObserver,内部包裹了mOwner、mObserver、mActive、mLastVersion变量,如下:
- ObserverWrapper抽象类
private abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;// 初始值是-1,LiveData的初始值是0,那么第一次对比时// observer.mLastVersion肯定是小于mVersion的,一定会更新一次值int mLastVersion = START_VERSION;
}
- ObserverWrapper的实现类LifecycleBoundObserver
在ObserverWrapper包装mLastVersion和mObserver的基础上,把LifecycleOwner(中文咋说?生命周期所有者嘛?这不重要,方正不外乎Activty、Fragment、或者自己实现的自定义生命周期组件)也包装了进来,
主要是为了拿到当前Activity/Fragment的生命周期状态,做一些逻辑,比如:onDestroy时要removeObserver,判断当前是否时活跃状态,也就是isAtLeast(STARTED)状态,我们在编写业务代码时也经常传递这个参数给到子模块,完成对于生命周期组件的状态判断、添加、移除生命周期监听的observer对象等逻辑。
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {@NonNull final LifecycleOwner mOwner;// 将LifecycleOwner对象包装到其中,用户实现一些生命周期的逻辑,// 也能直接拿到lifecycle对象,其实现就是Activity、Fragment。LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {mOwner.getLifecycle().removeObserver(this);}}
1.3 观察者集合的存储:SafeIterableMap<Observer<? super T>, ObserverWrapper>,以obser为key,ObserverWrapper对value
这是在androidx.core.common库中定义的一个map结构,仔细看下其实是一个链表实现的,实现的是迭代器接口。
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =new SafeIterableMap<>();
- 疑问:为什么不是使用HashMap、ArrayMap等现有集合,非要自己实现一个映射表?炫技嘛?如果被面试问到你能搭粗来嘛?可以把答案打到评论区!!
2、onChange()执行时机
-
(1)onStart()之后调用setValue()立即回调onChanged(newData)
-
(2)调用observe()时,如果Observer是新建的实例,那么其绑定的mLastVersion初始值是-1,当调用lifecycleOwner.addObserver()时会调用activeStateChanged(), 然后触发dispatchingValue(), 因为不满足observe.mLastVersion>=mVersion(默认值是0,每次setValue/postValue加1),然后回调onChanged(newData)
如果是在onStop()中注册observer,那么会在回到onStart()后会回调一次onChanged()
⚠️ LiveData中判断LifecycleOwner是判断生命周期状态是否是isAtLeast(START),所以onPause时调用observe()方法也是会回调onChanged()方法的。 -
(3)LifecycleOwner生命周期变化时,还存在同因生命周期<Lifecycle.State.ON_START并没有回调onChanged(newData),则会回调一次onChanged(),把最新的mData值回调给onChanged()方法
3、说LiveData会数据倒灌是这么回事?
3.1 本质
LiveData其实本质上实现的是将事件发送时机限定在LifecycleOwner的生命周期内的粘性事件。LiveData在生命周期可见时,将不可见时更新的mData版本回传到onChanged()中,当setValue()是在observe()之前调用的,那调用observe()时会把前面setValue的最新的值传给观察者的onChanged()。这个被观察者的变量是否有更新过的逻辑,主要靠LiveData类中定义的mVersion和ObserverWrapper的mLastVersion对比逻辑来实现的。
- 分发更新后的值给观察者是调用dispatchingValue方法实现的,分别在setValue和onActiveStateChanged时调用,
setValue调用分发value的逻辑很好理解,onActiveStateChanged方法在调用observe()方法增加新观察者时也会调用,然后根据版本决定是否调用observer的onChangd方法。
@SuppressWarnings("unchecked")private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.//// we still first check observer.active to keep it as the entrance for events. So even if// the observer moved to an active state, if we've not received that event, we better not// notify for a more predictable notification order.if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData);}
不使用LiveData进行数据更新时,一般是在UI控件变量准备好了,然后获取数据,再把数据传递给UI控件变量的某个属性,实现UI的更新,这种命令式UI的开发模式本身没有什么问题,符合日常的开发逻辑。
但是因为Android的UI架构都是基于Activity/Fragment生命周期管理的,就必然存在数据异步获取到时界面已经处于不活跃状态的情况。甚至很可能因为内存不足已经销毁了,然后在用户操作下回到活跃状态了,那么异步拿到的数据可能是没有塞给UI控件的,一般都是在onResume时又异步获取一次数据,然后更新到UI控件上。
3.2 使用LiveData实现事件注册-分发逻辑的问题
基于3.1对于LiveData数据更新本质的分析,如果我们使用LiveData的setValue/postValue,然后通过observe()分发进行监听,使用String/Int/CustomEvent(自定义的事件类,类似于EventBus)等数据做为事件分发。虽然这种方式能够避免内存泄漏,但是事件是粘性的,先发送事件,然后注册也会接收到该事件,这样的逻辑实际上并不是我们日常业务的事件逻辑。
google官方sample只处理一次的LiveData事件实现方案:
官方todoapp的Event实现
ps: 不得不说,老外的文章写得还是蛮清楚的,循序渐进,解释各种方案的问题,层层递进,对读者更好的理解有莫大帮助。
3.3 短时间内连续执行多次postValue,只有最后一次更新的值会收到数据变化的onChanged回调
这个问题是因为postValue的实现逻辑导致的,如下是postValue的代码:
protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}
};
第一次postValue,postTask是true,mPendingData赋值为value,首次调用因为mPending值是NOT_SET,所以postTask为true, 会调用postToMainThread方法。
然后同步短时间内再次调用postValue,这时候因为mPostValueRunnable中的逻辑还未执行,所以mPendingData会赋值为新的value值,但是这次因为mPending不再是NOT_SET,而是第一次调用的值了,所以postTask为false,postValue方法直接return了。
然后在mPostValueRunnable.run方法被执行到时,mPendingData已经是最后一次调用postValue时传入的值了,所以mPostValueRunnable中调用的setValue方法,最终回调到Observer#onChanged的是最后一次postValue传入的值。
- 同时,官方对postValue的备注也有提醒,如下所示:
- 可见,官方文档的描述更严谨,是说主线程的mPostValueRunnable被执行之前,多次调用postValue传入的值,最后只会传给观察者最后一次的值。
- 实际写一个例子说明这个现象:
// 按钮点击是,连续多次执行postValue
simpleViewModel.liveData1.postValue("我是新数据0")
simpleViewModel.liveData1.postValue("我是新数据1")
simpleViewModel.liveData1.postValue("我是新数据2")
simpleViewModel.liveData1.postValue("我是新数据3")
// onChanged回调的结果只有最后一次
23:56:22.732 D liveData1: onChanged newValue = 我是新数据3// 如果给两次postValue之间加间隔呢?
simpleViewModel.viewModelScope.launch(Dispatchers.IO) {simpleViewModel.liveData1.postValue("我是新数据0")delay(10)simpleViewModel.liveData1.postValue("我是新数据1")delay(10)simpleViewModel.liveData1.postValue("我是新数据2")delay(10)simpleViewModel.liveData1.postValue("我是新数据3")
}
// 输出结果如下,就没有出现丢失数据的更新的情况了。并且尝试把间隔时间改成1ms,多次操作会出现可能丢失部分数据,
// 可能全部不丢失的,这里能说明上面关于postValue短时间内连续更新数据,可能只有最后一次分发给观察者的原因了。
// !!这里也能说明,使用LiveData实现事件分发,要也别注意异步分发事件可能丢事件的。
// 这里也说明了LiveData在异步数据流上是存在缺陷的,当然google又出了Flow组件专门用于处理数据流,待后续会分享其用法。
00:03:29.143 D liveData1: onChanged newValue = 我是新数据0
00:03:29.199 D liveData1: onChanged newValue = 我是新数据1
00:03:29.212 D liveData1: onChanged newValue = 我是新数据2
00:03:29.232 D liveData1: onChanged newValue = 我是新数据3
- 截止目前LiveData已经有两个需要我们特别关注,可能彩坑的点了:
(1)数据粘性更新;
(2)异步更新数据的postValue可能丢失更新过程中的值,不适用于异步数据流的更新&展示。
比如:要实现进度的展示,用LiveData#postValue就可能出现进度变化过程丢一部分了,UI要的一些变化过程没了(貌似这个举例不妥~),作为事件总线分发事件可能丢失等情况;
参考文章
【1】LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)