zgq105 / blog

2 stars 0 forks source link

Livedata监听数据变化和通知的过程解析 #98

Open zgq105 opened 2 years ago

zgq105 commented 2 years ago

在lifecycle篇章中说过livedata监听Activity生命周期来控制livedata中的激活状态,如果处于激活状态才通知观察者数据变更。接下来通过分析setValue和postValue的过程来剖析Livedata监听数据变更的全过程。

1.setValue方法

平时我们经常会通过 mutableLiveData.value = "zgq"的方式去更新数据和通知。接下来看看setValue中做了哪些操作,如下:

@MainThread
    protected void setValue(T value) { 
        assertMainThread("setValue");//需要在主线程调用
        mVersion++; //每次赋值版本号+1
        mData = value; //赋值,mData是volatile修饰的Object类型的变量,保证多线程的可见性和防止指令重排
        dispatchingValue(null);  //业务分发
    }
void dispatchingValue(@Nullable ObserverWrapper initiator) {
 //...
              for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue()); //遍历所有的观察者,通知数据有变化
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
}

通知观察者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);  //通知外面业务数据变更
    }

2.postValue方法

平时我们经常会通过 mutableLiveData.postValue("zgq")的方式去更新数据和通知。接下来看看postValue中做了哪些操作,如下:

protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) { //加锁保证线程安全
            postTask = mPendingData == NOT_SET;
            mPendingData = value; //mPendingData是volatile修饰的Object类型,保证线程可见性
        }
        if (!postTask) {
            return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); //这个方法最终转到主线程中调用,通过Handler
的方式
    }

postToMainThread的内部实现是在DefaultTaskExecutor类中,如下:

@Override
    public void postToMainThread(Runnable runnable) {
        if (mMainHandler == null) {
            synchronized (mLock) {
                if (mMainHandler == null) {
                    mMainHandler = createAsync(Looper.getMainLooper());
                }
            }
        }
        //noinspection ConstantConditions
        mMainHandler.post(runnable); //主线程Handler
    }

接下来看下mPostValueRunnable的内部实现,如下:

private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
                mPendingData = NOT_SET;
            }
            setValue((T) newValue); //最终调用还是调用到setValue方法
        }
    };

总结:setValue是在要求主线程中调用;postValue是异步调用的,通常适用于子线程调用。

3.livedata数据倒灌问题(粘性问题)

viewModel.testData.value = "hello world"

viewModel.testData.observe(viewLifecycleOwner) {
            Log.d("zgq", "testData:$it")
        }

上面这段代码会输出testData:hello world,因此发生了数据倒灌的问题,即把旧的数据发送给新添加的观察者。

原因分析: 因为LiveData内部使用的是版本号机制,mVersion是LiveData类的全局变量,默认是0或者-1,取决于调用的构造函数。 ObserverWrapper类中的全局变量mLastVersion默认是-1。根据下面的逻辑判断:是会触发数据倒灌:

 private void considerNotify(ObserverWrapper observer) {
       //省略
      //第一次observer.mLastVersion是-1,而mVersion是1或者0,因为setValue是mVersion执行了++的操作。所以触发了旧数据通知到新的观察者的情况
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

4.livedata数据倒灌问题解决方案

解决方式很多,可以参考:链接

5.常见问题分析:

1.发起网络请求,然后界面切后台,接口返回数据,livedata数据更新了,此时界面的livedata观察者会立即收到数据吗?

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {

      @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); //至少STARTED之后才认为是活跃状态
        }

        @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();
            }
        }
}
void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            mActive = newActive;
            changeActiveCounter(mActive ? 1 : -1);
            if (mActive) { //如果是活跃状态
                dispatchingValue(this); //分发出去通知所有观察者
            }
 }
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);
    }

结论:以上情景,切到后台之后观察者并不会马上收到接口数据的变更,只有再次切换回前台才会通知观察者。