Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zgq105 opened this issue May 21, 2022 · 0 comments
Open

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

zgq105 opened this issue May 21, 2022 · 0 comments

Comments

@zgq105
Copy link
Owner

zgq105 commented May 21, 2022

在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);
    }

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant