LiveData 源碼解析

LiveData 源碼解析

之前做過一篇關(guān)于Lifecycle的源碼解析,里面分析了

  1. 生命周期擁有者如何進行生命周期的感知(通過Fragment)
  2. 當(dāng)生命周期變化時,如何進行進行通知:將Obsever進行包裝竿滨,生成LifecycleEventObserver的具體實現(xiàn)來,然后在生命周期變化時涕癣,調(diào)用其對應(yīng)的狀態(tài)的分發(fā)彬祖。

在通常進行使用的過程中,我們都是將數(shù)據(jù)通過 LiveData 進行一層包裝纽竣,然后就可以進行其數(shù)據(jù)的變化監(jiān)聽了墓贿,那么其具體是如何實現(xiàn)的呢茧泪?

慣例,先來個簡單的測試demo

object SplashViewModel{
    var logined = MutableLiveData<Boolean>()
    init {
        logined.postValue(true)
    }
}

class SplashActivity : BaseBindingActivity<ActivitySplashBinding, SplashViewModel>() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        SplashViewModel.logined.observe(this, Observer { print(it)})
    }
}

只需要一個簡單的 observe() 方法聋袋,就可以實現(xiàn)生命周期的監(jiān)聽队伟,然后將數(shù)據(jù)發(fā)送到我們的Activity中,我們看看這個方法里面到底做了什么

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // 頁面銷毀幽勒,直接返回
        return;
    }
    //包裝
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        //如果已經(jīng)存在嗜侮,拋異常
        throw new IllegalArgumentException("Cannot add the same observer with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    //增加一個監(jiān)聽者
    owner.getLifecycle().addObserver(wrapper);
}

可以看到,只是將我們的生命周期擁有者和監(jiān)聽者進行了一次包裝啥容,生成了 LifecycleBoundObserver 類锈颗,然后將它添加到監(jiān)聽者列表中。

在之前的Lifecycle的源碼解析文章中咪惠,我們了解到击吱,當(dāng)頁面發(fā)生變化時,會調(diào)用監(jiān)聽者的 onStateChanged() 方法遥昧。

        @Override
        boolean shouldBeActive() {//判斷當(dāng)前頁面是否屬于激活狀態(tài)(即可見狀態(tài))
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                                   @NonNull Lifecycle.Event event) {
            //如果頁面銷毀了覆醇,則直接移除當(dāng)前對應(yīng)的監(jiān)聽者
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            //進行狀態(tài)的變更
            activeStateChanged(shouldBeActive());
        }

所以當(dāng)界面的生命周期變化時,會調(diào)用 activeStateChanged() 來進行狀態(tài)的變更處理

        //進行狀態(tài)的轉(zhuǎn)變
        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            //LiveData的激活的觀察者數(shù)量進行變化
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                //原來沒有激活的觀察者炭臭,現(xiàn)在有了新增的
                // 說明LiveData從無激活的觀察者->有激活的觀察者
                onActive();//留下鉤子永脓,給繼承者使用
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                //當(dāng)前頁面未激活,并且變化后徽缚,LiveData中處于激活狀態(tài)的觀察者數(shù)量為0憨奸,
                // 說明LiveData從有激活的觀察者->無激活的觀察者
                onInactive();//留下鉤子,給繼承者使用
            }
            if (mActive) {//如果頁面變化為了激活狀態(tài)凿试,那么進行數(shù)據(jù)的分發(fā)
                dispatchingValue(this);
            }
        }
    }

這里主要根據(jù)頁面的激活數(shù)排宰,預(yù)留了兩個鉤子函數(shù),用戶可以做一些自己的數(shù)據(jù)處理那婉。最主要的還是 dispatchingValue() 中的數(shù)據(jù)處理板甘。

    //分發(fā)數(shù)據(jù)
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            //如果正在分發(fā),則將mDispatchInvalidated置為true详炬,那么在分發(fā)過程中盐类,會根據(jù)這個標(biāo)志位重新新數(shù)據(jù)的分發(fā)
            mDispatchInvalidated = true;
            return;
        }
        //標(biāo)記正在進行數(shù)據(jù)的分發(fā)
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {//如果有對應(yīng)的監(jiān)聽者,直接分發(fā)給對應(yīng)的監(jiān)聽者
                considerNotify(initiator);
                initiator = null;
            } else {
                //遍歷所有的觀察者呛谜,然后進行數(shù)據(jù)的分發(fā)在跳,
                // 如果分發(fā)過程中,發(fā)現(xiàn)mDispatchInvalidated變化了隐岛,那么說明有新的數(shù)據(jù)變更猫妙,則退出當(dāng)前混選,然后從新分發(fā)新的數(shù)據(jù)
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                     mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
    
    //通知某個觀察者進行了數(shù)據(jù)的變化
    private void considerNotify(ObserverWrapper observer) {
        //觀察者未激活聚凹,返回
        if (!observer.mActive) {
            return;
        }
        //觀察者當(dāng)前狀態(tài)為激活割坠,但是當(dāng)前變?yōu)榱瞬豢梢姞顟B(tài)齐帚,那么調(diào)用
        //activeStateChanged方法
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        //如果數(shù)據(jù)版本已經(jīng)是最新的了,那么直接返回
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        //修改數(shù)據(jù)版本號
        observer.mLastVersion = mVersion;
        //調(diào)用監(jiān)聽者的onChanged方法
        observer.mObserver.onChanged((T) mData);
    }

在數(shù)據(jù)分發(fā)過程中彼哼,根據(jù)相應(yīng)的觀察者數(shù)據(jù)版本號对妄,然后和當(dāng)前的數(shù)據(jù)的版本號進行比較,如果是新的數(shù)據(jù)敢朱,那么調(diào)用觀察者的 onChange()方法剪菱,也就是我們在開始時寫的測試demo中的 print(it)

總結(jié)一下頁面發(fā)生變化時拴签,數(shù)據(jù)的處理流程:

  1. 當(dāng)頁面發(fā)生變化琅豆,從不可見變?yōu)榭梢姇r,會將LiveData中的數(shù)據(jù)版本號跟對應(yīng)的觀察者中的版本號進行比較篓吁,如果大,則調(diào)用onChanged()進行數(shù)據(jù)的回調(diào)蚪拦。
  2. 如果頁面為不可見杖剪,那么不會進行數(shù)據(jù)的回調(diào)處理。

那么當(dāng)我們使用 setValue() 驰贷,或者 postValue() 時盛嘿,LiveData 又是做了什么處理呢?

我們先看看 setValue()

    protected void setValue(T value) {
        assertMainThread("setValue");
        //記錄當(dāng)前數(shù)據(jù)的版本號
        mVersion++;
        //記錄設(shè)置的數(shù)據(jù)值
        mData = value;
        //進行數(shù)據(jù)的分發(fā)
        dispatchingValue(null);
    }

可以看到弓颈,直接將數(shù)據(jù)版本號+1渣玲,然后進行了數(shù)據(jù)的分發(fā)贮折,dispatchingValue() 我們剛才進行過分析,如果參數(shù)為null芥炭,那么會遍歷所有的監(jiān)聽者,逐個通知所有觀察者進行了數(shù)據(jù)的變化(前提是觀察者處于激活狀態(tài))恃慧。

我們再看看 postValue()

     protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if (!postTask) {
            return;
        }
        //通過線程池分發(fā)到主線程去處理
        ArchTaskExecutor.getInstance().postToMainThread(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);
        }
    };

可以看到园蝠, postValue() 通過線程池技術(shù),將數(shù)據(jù)在主線程進行了 setValue()痢士。

匯總

1.當(dāng)生命周期不可見時彪薛,會將最新的數(shù)據(jù)保存在LiveData中,然后保存相應(yīng)的版本號怠蹂,當(dāng)其可見時善延,會將數(shù)據(jù)變化通知
2.當(dāng)LiveData中的數(shù)據(jù)變化時,會遍歷所有的監(jiān)聽頁面城侧,然后進行數(shù)據(jù)的變化通知易遣。

附帶一張ObserverWrapper 的結(jié)構(gòu)圖

image-20200304141507165

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赞庶,一起剝皮案震驚了整個濱河市训挡,隨后出現(xiàn)的幾起案子澳骤,更是在濱河造成了極大的恐慌,老刑警劉巖澜薄,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件为肮,死亡現(xiàn)場離奇詭異,居然都是意外死亡肤京,警方通過查閱死者的電腦和手機颊艳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忘分,“玉大人棋枕,你說我怎么就攤上這事《事停” “怎么了重斑?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肯骇。 經(jīng)常有香客問我窥浪,道長,這世上最難降的妖魔是什么笛丙? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任漾脂,我火速辦了婚禮,結(jié)果婚禮上胚鸯,老公的妹妹穿的比我還像新娘骨稿。我一直安慰自己,他們只是感情好姜钳,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布坦冠。 她就那樣靜靜地躺著,像睡著了一般傲须。 火紅的嫁衣襯著肌膚如雪蓝牲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天泰讽,我揣著相機與錄音例衍,去河邊找鬼。 笑死已卸,一個胖子當(dāng)著我的面吹牛佛玄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播累澡,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梦抢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愧哟?” 一聲冷哼從身側(cè)響起奥吩,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哼蛆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后霞赫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腮介,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年端衰,在試婚紗的時候發(fā)現(xiàn)自己被綠了叠洗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡旅东,死狀恐怖灭抑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抵代,我是刑警寧澤腾节,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站荤牍,受9級特大地震影響禀倔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜参淫,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愧杯。 院中可真熱鬧涎才,春花似錦、人聲如沸力九。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跌前。三九已至棕兼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抵乓,已是汗流浹背伴挚。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灾炭,地道東北人茎芋。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蜈出,于是被迫代替她去往敵國和親田弥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • LiveData是Google發(fā)布的一個架構(gòu)組件铡原,它是一個數(shù)據(jù)持有類偷厦,并且數(shù)據(jù)可以被觀察商叹。區(qū)別于普通的被觀察者,L...
    小小的coder閱讀 129評論 0 0
  • LiveData是Google發(fā)布的一個架構(gòu)組件只泼,它是一個數(shù)據(jù)持有類剖笙,并且數(shù)據(jù)可以被觀察。區(qū)別于普通的被觀察者辜妓,L...
    lxbnjupt閱讀 602評論 0 2
  • LiveData 使用最新的 androidx 的源碼枯途。 前言 LiveData 是 Android Archit...
    ldlywt閱讀 944評論 0 2
  • 掘金文章鏈接 基于:macOs:10.14/AS:3.4/Android build-tools:28.0.0 思...
    冰川孤辰j(luò)s閱讀 1,563評論 0 2
  • 0.前言 關(guān)于livedata的使用詳情見LiveData+ViewModel+RxJava2+autoDispo...
    雯藝雪閱讀 1,006評論 1 2