【经验】LiveData使用常见问题

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();
	    }
	}
  • 20
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值