LiveData在数据驱动设计时非常好用。结合ViewModel,可以对Fragment或者Activity组件进行数据绑定,无需开发者另外对数据生命周期管理(framework层实现)。然而由于LiveData在设计时的特性,使得在使用LiveData时,可能会遇到一些意想不到的问题。下面结合案例来谈谈使用过程中常见的问题
Sticky粘性
现象
在livedata observer前进行setvalue操作,或者在oncreate中进行obsever操作没有进行setvalue/postvalue操作。在activity生命周期发生变化时会监听到livedata数据变化。
原因
除了setvalue/postvalue方法外,livedata会监听Owner宿主生命周期变化,通知观察者;
livedata.observe注册监听
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); //关联owner
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);
}
LifecycleBoundObserver中监听宿主生命周期,在生命周期变化时会调用activeStateChanged方法
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
activeStateChanged方法最终通知数据变动considerNotify
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);
}
其中mLastVersion初始值为-1,mVersion初始值为0。因此在没有任何设置值操作,仅凭宿主生命周期变动,也会带来数据被监听到变动。其实这样的设计是合理的,符合liveData设计初衷:数据和UI生命周期绑定,这种和宿主生命周期挂钩的数据我们通常叫做State数据。有时候我们需要的是类似EventBus注册监听事件,这样的数据我们叫做Event数据,google基于LiveData新增了SingleLiveEvent来解决sticky特性带来的多次回调监听问题
SingleLiveEvent
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
@Override
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) { //2. 如果mpending为true,设置为false,并且返回true;否则不做改动。因此只有在数据变动时才会进入到observer.onChanged方法
observer.onChanged(t);
}
}
});
}
@MainThread
@Override
public void setValue(@Nullable T t) {
mPending.set(true);//1. 每次数据变化时候,将mPending设置为true
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {//没啥太多用处,仅封装了setValue(null)
setValue(null);
}
}
这样就可以避免sticky问题的产生,数据仅在setValue后才能被observer监听到。但同时也会带来另外的问题:一旦有多个observe监听数据,此时仅有第一个observer能监听到数据变化,其他observer无效。针对这个问题可以参考KunMinX大佬的UnPeekLiveData
postValue数据丢失
现象
频繁对某个liveData进行操作时,可能会导致中间某些值丢失。
原因
postValue中会判断前一个value是否已经被消费;如果没有被消费,当前value将会配抛弃。这样做可以避免频繁刷新
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;// 1. 赋值
}
if (!postTask) { //如果上一个值没有被设置为not_set,这里直接丢弃value
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
volatile Object mPendingData = NOT_SET;
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
mPendingData = NOT_SET;
}
setValue((T) newValue);//2. 最终调用setValue
}
};
解决方案一
推荐使用kotlin的flow组件
图解flow管道
官方文档
解决方案二
封装现有LiveData。将现有的liveData封装到SafeCount类中进行操作。可以仿造下面的SafeCount写其他类似的liveData,也可以构造一个通用类
public class SafeCount {
private final AtomicInteger targetCount = new AtomicInteger(0);
private final AtomicInteger tmpCount = new AtomicInteger(0);
private final ConcurrentLinkedQueue<Integer> pendingData = new ConcurrentLinkedQueue<>();
private final MutableLiveData<AtomicInteger> liveData;
SafeCount(MutableLiveData<AtomicInteger> value) {
liveData = value;
}
void reset() {
targetCount.set(0);
tmpCount.set(0);
liveData.setValue(tmpCount);
}
void increment() {
pendingData.offer(targetCount.incrementAndGet());
XThread.runOnUiThread(() -> {
while (!pendingData.isEmpty()) {
Integer value = pendingData.poll();
if (value != null) {
tmpCount.set(value);
liveData.setValue(tmpCount);
}
}
});
}
}
//XThread.java
public class XThread {
private final static Handler sMainHandler = new Handler(Looper.getMainLooper());
public static void runOnUiThread(Runnable runnable) {
if(isOnMainThread()){
runnable.run();
}else {
sMainHandler.post(runnable);
}
}
public static boolean isOnMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}