天天看點

鬥魚直播一面之LiveData粘性資料的原因分析總結

作者:搬磚小碼龍

#頭條創作挑戰賽#

本文通過在鬥魚直播遇到的問題總結而出,如有不對的地方,請及時批評指正。篇幅較長,請耐心閱讀。

大家在日常開發工作中使用LiveData是否遇到過這樣一個場景,使用LiveData發送資料時,當手機螢幕發生旋轉時,資料會重新自動發送一遍。這個就是經典的LiveData資料倒灌問題,也被稱為資料粘性問題。

本文從以下幾個子產品進行分析,由簡到繁,深入源碼分析,幫助深入了解LiveData實作原理,追本溯源,從根本上解決問題。

  • 簡介
  • 基本使用
  • 源碼分析之生命周期綁定
  • 源碼分析之資料發送
  • 資料倒灌問題解決方案

簡介

LiveData最為Jetpack元件重要元件之一,是一種可觀察的資料存儲類,支援存儲多種資料類型。

鬥魚直播一面之LiveData粘性資料的原因分析總結

基本使用

建立一個LiveData執行個體,添加資料。

class VideoDetailModel : ViewModel() {

    val shareCodeLiveData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }
fun getShareCode(dto: ShareCode) {
        //切換IO
        viewModelScope.launch(Dispatchers.IO) {
         asyncTask {
                //網絡請求
                getService(TieApi::class.java).getShareCode(dto)
            }.result.data.also {
             //改變資料源,通知觀察者
             shareCodeLiveData.post(it)
            }
        }
    }
}           

在Activity中綁定LiveData并監聽資料變化。

//在activity中觀察資料變化
shareCodeLiveData.observe(this)Observer { 
          //根據code 進行UI展示
       }           

源碼分析

生命周期綁定

LiveData使用observe 綁定Activity生命周期,我們從源碼層面來看一下observe是怎麼綁定Activity生命周期的。

1.Activity作為LifecycleOwner接口的實作者,可以通過LifecycleOwner的getLifecycle()方法擷取LifeCycle。并綁定目前Activity生命周期。當生命周期發生變化時,會通知LiveData。

@MainThread //主線程
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        //通過lifecycle判斷目前activity狀态,如果是destory 則不會監聽資料變化
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        //關聯一下observer和LifecycleOwner
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        //儲存觀察者,如果已經添加過,則傳回對應的value,否則傳回null
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
       ..............省略部分代碼.......
      
       //注冊LifeCycle監聽,綁定生命周期
        owner.getLifecycle().addObserver(wrapper);
    }           

2.LifecycleBoundObserver不僅儲存了 LifecycleOwner 和 observer。還繼承了LifecycleEventObserver,當LifecycleOwner生命周期發生變化時,會調用LifecycleEventObserver.onStateChanged方法。

@Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            //通過lifecycleOwner擷取目前activity生命周期狀态
            Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
            //如果activity被銷毀則移除監聽
            if (currentState == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
           //記錄Activity上一個生命周期狀态
            Lifecycle.State prevState = null;
            //更新活躍狀态
            while (prevState != currentState) {
                prevState = currentState;
                //改變活躍狀态STARTED
                activeStateChanged(shouldBeActive());
                currentState = mOwner.getLifecycle().getCurrentState();
            }
        }
        //目前生命周期為STARTED時,為活躍狀态
            @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }           

3.LifecycleBoundObserver是LifeCycle觀察者的一個子類,通過觀察LifecycleOwner生命周期狀态,改變LiveData的監聽。

void activeStateChanged(boolean newActive) {
            //如果目前狀态和上一個狀态一樣,則已經注冊過了,不需要處理
            if (newActive == mActive) { 
                return;
            }
            
            //更新目前活躍狀态
            mActive = newActive;
            //修改活躍狀态數量
            changeActiveCounter(mActive ? 1 : -1);
            if (mActive) {
                //分發資料
                dispatchingValue(this);
            }
        }           

LiveData通過擷取Activity的LifeCycle來判斷目前Activity顯示狀态,判斷條件為Activity生命周期為onStart時,進行注冊監聽,資料分發。

資料分發

1 .當Activity目前生命周期為onStart時,調用 dispatchingValue(this)進行資料分發。下面我們來看一下具體資料是怎麼進行分發的?

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        //是否正在進行資料分發
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            //如果觀察者不為null
            if (initiator != null) {
                //分發資料給目前觀察者
                considerNotify(initiator);
                initiator = null;
            } else {
                //目前觀察者為null,則分發資料給其他所有觀察者
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }           

2.不管注冊多少觀察者,最後都會調用considerNotify進行資料的具體分發操作。我們接着往下看。

private void considerNotify(ObserverWrapper observer) {
        //如果目前觀察者不處于活躍狀态 ,則傳回退出
        if (!observer.mActive) {
            return;
        }
     //如果目前觀察者不處于活躍狀态 ,則更新狀态後 傳回退出
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //如果觀察者的版本小于Livedata中記錄的版本,則傳回
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //将目前觀察者版本更新給mLastVersion
        observer.mLastVersion = mVersion;
        //通知觀察者資料變化
        observer.mObserver.onChanged((T) mData);
    }           

資料分發時,首先判斷目前觀察者Activity是否處理活躍狀态(onStart),并且目前觀察者的版本是否小于Livedata中記錄的版本。隻有目前觀察者的版本小于LiveData中記錄的版本才會進行資料分發。

發送同步資料

@MainThread //強制主線程
    protected void setValue(T value) {
        assertMainThread("setValue");
        //将目前版本加1
        mVersion++; 
        mData = value;
        //通知是以觀察者更新資料
        dispatchingValue(null);
    }           

LiveData.setValue隻能在主線程中調用,同時使用mVersion記錄目前LiveData資料版本,然後直接調用dispatchingValue(null) 進行資料分發。

發送異步資料

protected void postValue(T value) {
        boolean postTask;
        //加鎖
        synchronized (mDataLock) {
            //目前沒有資料處理,将postTask 設定位true
            postTask = mPendingData == NOT_SET;
            //将資料交個mPendingData
            mPendingData = value;
        }
        //如果前面還有資料需要處理,則傳回
        if (!postTask) {
            return;
        }
        //将資料處理postToMainThread交給線程池處理
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }           

LiveData.postValue将資料發送到線程池中處理并切換至主線程,使用postTask用來标記前面是否還有資料處理。我們來看一下mPostValueRunnable中的資料處理。

private final Runnable mPostValueRunnable = new Runnable() {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            Object newValue;
            //加鎖
            synchronized (mDataLock) {
               //将需要處理的資料交給newValue
                newValue = mPendingData;
                //重置mPendingdata
                mPendingData = NOT_SET;
            }
            //分發資料
            setValue((T) newValue);
        }
    };
           

run()方法中主要是調用setValue進行資料分發,當資料處理完将mPendingData恢複到初始值。

資料倒灌問題解決方案

出現此問題的原因在于:如果目前螢幕發生旋轉,Activity會發生銷毀重建時導緻觀察者Activity被移除監聽。當Activity重建的時候會重新添加一個新的觀察者,由于ViewModel可以儲存LiveData資料不被回收。當Activity生命周期變成onStart時,新的觀察者的版本号為初始值-1,小于LiveData中記錄的版本号mVersion,會導緻資料再次被分發一遍。

關于ViewModel儲存資料可參考這篇文章:《網易一面之ViewModel存儲原理面試總結》。

private void considerNotify(ObserverWrapper observer) {
        //如果目前觀察者不處于活躍狀态 ,則傳回退出
        if (!observer.mActive) {
            return;
        }
     //如果目前觀察者不處于活躍狀态 ,則更新狀态後 傳回退出
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //如果觀察者的版本小于Livedata中記錄的版本,則傳回
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //将目前觀察者版本更新給mLastVersion
        observer.mLastVersion = mVersion;
        //通知觀察者資料變化
        observer.mObserver.onChanged((T) mData);
    }           

解決方案

當Activity恢複重建時,通過反射技術修改觀察者中的mLastVersion版本号,使mLastVersion等于LiveData中記錄的版本号mVersion,此時observer.mLastVersion >= mVersion這個判斷條件成立,不會進行資料分發。

以上就是鬥魚直播面試後總結的幾個要點,還不會的同學趕緊學起來吧,感謝您的閱讀,創造不易,如果您覺得本篇文章對您有幫助,請點選關注小編,您的支援就是小編創作的最大動力

繼續閱讀