LiveData數(shù)據(jù)通知解析以及死鎖問(wèn)題處理

前言

LiveDataAndroid常用的組件芬为,它代表具有生命周期的數(shù)據(jù)欢搜,是MVVM框架組成不可或缺的一部分

img.jpg

問(wèn)題場(chǎng)景

在我們的代碼案例中,我們點(diǎn)擊A頁(yè)面List中的一個(gè)Item,然后將數(shù)據(jù)傳遞并跳轉(zhuǎn)到B Fragment恃疯,然后在B Fragment中使用LiveData對(duì)數(shù)據(jù)進(jìn)行業(yè)務(wù)處理從而展示儿捧,但是由于編碼問(wèn)題導(dǎo)致當(dāng)點(diǎn)擊某一條Item出錯(cuò)時(shí)荚坞,點(diǎn)擊后續(xù)的Item都無(wú)法正常顯示B FragmentUI造成白屏的現(xiàn)象,由于這個(gè)問(wèn)題我對(duì)LIveData的數(shù)據(jù)通知方式的源碼做了分析

源碼分析

當(dāng)我們對(duì)LivaData進(jìn)行賦值并且它處于活動(dòng)狀態(tài)時(shí)會(huì)調(diào)用

    @MainThread
    protected void setValue(T value) {
       //斷言此函數(shù)是在主線程執(zhí)行,否則會(huì)拋出異常
        assertMainThread("setValue");
       //將此LiveData的數(shù)據(jù)版本加一
        mVersion++;
       //LiveData中真實(shí)存儲(chǔ)內(nèi)容的字段菲盾,getValue返回的就是此字段的值
        mData = value;
      //進(jìn)行數(shù)據(jù)通知處理
        dispatchingValue(null);
    }

dispatchingValue函數(shù)中有兩個(gè)重要的變量颓影,mDispatchingValuemDispatchInvalidated,并且這兩個(gè)字段只有在這個(gè)函數(shù)中有使用懒鉴,之所以不做成局部變量是因?yàn)檫@兩個(gè)字段控制的是所有此LiveData數(shù)據(jù)分發(fā)的行為诡挂,而不僅是本次通知的行為

mDispatchingValue表示數(shù)據(jù)是否正在調(diào)度中碎浇,如果上一次設(shè)置的數(shù)據(jù)正在分發(fā)中則會(huì)return掉本次數(shù)據(jù)通知操作
mDispatchInvalidated表示數(shù)據(jù)調(diào)度是否失效,如果在本次設(shè)置數(shù)據(jù)調(diào)度的過(guò)程中又收到新的調(diào)度璃俗,則本次調(diào)度就會(huì)失效

    void dispatchingValue(@Nullable ObserverWrapper initiator) {
       //是否正在進(jìn)行數(shù)據(jù)調(diào)度
        if (mDispatchingValue) {
           //如果正在進(jìn)行數(shù)據(jù)調(diào)度則上次正在執(zhí)行的數(shù)據(jù)調(diào)度標(biāo)記已失效
            mDispatchInvalidated = true;
           //如果正在進(jìn)行數(shù)據(jù)調(diào)度則本次數(shù)據(jù)調(diào)度直接return取消
            return;
        }
       //將是否正在進(jìn)行數(shù)據(jù)調(diào)度的值設(shè)置為true奴璃,表示數(shù)據(jù)正在調(diào)度中
        mDispatchingValue = true;
      //下面這個(gè)循環(huán)就是數(shù)據(jù)調(diào)度的過(guò)程,它會(huì)遍歷所有注冊(cè)的觀察者城豁,對(duì)他們通知新的數(shù)據(jù)
        do {
            //在數(shù)據(jù)調(diào)度開(kāi)始時(shí)將是否失效字段置為false
            mDispatchInvalidated = false;
           //當(dāng)調(diào)用setValue時(shí)initiator為null
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                     //內(nèi)層循環(huán)遍歷所有的觀察者考慮通知新的數(shù)據(jù)
                    considerNotify(iterator.next().getValue());
                    //一旦發(fā)現(xiàn)數(shù)據(jù)失效苟穆,則停止遍歷觀察者停止數(shù)據(jù)通知
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        //外層循環(huán)的執(zhí)行條件是數(shù)據(jù)失效,也就是說(shuō)如果在通知數(shù)據(jù)的過(guò)程中如果數(shù)據(jù)失效則會(huì)重新執(zhí)行一次do代碼塊唱星,從而通知最新的數(shù)據(jù)
        } while (mDispatchInvalidated);
       //表示數(shù)據(jù)調(diào)度結(jié)束雳旅,開(kāi)關(guān)打開(kāi),可以接受下一次的數(shù)據(jù)設(shè)置
        mDispatchingValue = false;
    }

dispatchingValue函數(shù)值你給我們可以看到
1.mDispatchingValue 字段用于表示數(shù)據(jù)通知是否正在執(zhí)行中
2.如果在執(zhí)行中又來(lái)了一條數(shù)據(jù)雖然由于mDispatchingValue 的緣故retun了本次執(zhí)行间聊,但是由于失效字段mDispatchInvalidated的控制do代碼塊會(huì)多次執(zhí)行攒盈,從而只會(huì)通知最新的數(shù)據(jù),這也就是為什么我們連續(xù)設(shè)置Livedata的值只有最后一次會(huì)生效的原因
considerNotify函數(shù)表示目標(biāo)觀察者考慮是否要通知數(shù)據(jù)更新

    private void considerNotify(ObserverWrapper observer) {
        //如果觀察者的生命周期不可用則放棄此次通知甸饱,生命周期依賴于Activity或者Fragment的生命周期
        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.
       //再次檢查是否為非活動(dòng)狀態(tài)沦童,并且額外的通知一次狀態(tài)變更
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
       //如果觀察者的數(shù)據(jù)版本已經(jīng)高于此次數(shù)據(jù)版本,則取消
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        //調(diào)用onChanged函數(shù)叹话,通知此次數(shù)據(jù)變更
        observer.mObserver.onChanged((T) mData);
    }

3.在函數(shù)dispatchingValue中如果數(shù)據(jù)過(guò)期偷遗,會(huì)在調(diào)用considerNotify之后才調(diào)用了break,這并不會(huì)造成通知舊數(shù)據(jù)的漏洞,因?yàn)樵?code>considerNotify函數(shù)中直接通知的最新的mVersion以及mData

問(wèn)題解決

我們可以看到considerNotify函數(shù)的最終目的是要去調(diào)用onChanged函數(shù),也就是我們監(jiān)聽(tīng)LiveData的代碼塊,我們會(huì)發(fā)現(xiàn)如果onChanged代碼塊中有異常拋出則函數(shù)dispatchingValue會(huì)直接從considerNotify(iterator.next().getValue())處直接中斷驼壶,造成mDispatchingValue的值無(wú)法變回false狀態(tài),以致于此LiveData再也無(wú)法正常的進(jìn)行數(shù)據(jù)通知的操作一直停留在了工作中的狀態(tài)氏豌,造成了一種類似死鎖的現(xiàn)象

4.為了防止dispatchingValue造成的死鎖,請(qǐng)關(guān)注你的onChanged代碼塊防止有異常出現(xiàn)破壞數(shù)據(jù)通知的邏輯

這也就印證了為什么在我們的業(yè)務(wù)中當(dāng)一條Item出錯(cuò)時(shí)热凹,再點(diǎn)擊其他的Item無(wú)法正常收到LiveData更新的現(xiàn)象泵喘,所以解決的方案就是在onChanged函數(shù),也就是我們監(jiān)聽(tīng)LiveData的代碼塊不要有異常拋出進(jìn)行異常修復(fù)或者特殊場(chǎng)景進(jìn)行try catch,要么就使用不會(huì)拋出異常的簡(jiǎn)單業(yè)務(wù)

總結(jié)

1.mDispatchingValue 字段用于表示數(shù)據(jù)通知是否正在執(zhí)行中般妙,防止數(shù)據(jù)通知操作重復(fù)

2.如果在執(zhí)行中又來(lái)了一條數(shù)據(jù)雖然由于mDispatchingValue 的緣故retun了本次執(zhí)行纪铺,但是由于失效字段mDispatchInvalidated的控制do代碼塊會(huì)多次執(zhí)行,從而只會(huì)通知最新的數(shù)據(jù)碟渺,這也就是為什么我們連續(xù)設(shè)置Livedata的值只有最后一次會(huì)生效的原因

3.在函數(shù)dispatchingValue中如果數(shù)據(jù)過(guò)期鲜锚,會(huì)在調(diào)用considerNotify之后才調(diào)用了break,這并不會(huì)造成通知舊數(shù)據(jù)的漏洞,因?yàn)樵?code>considerNotify函數(shù)中直接通知的最新的mVersion以及mData

4.為了防止dispatchingValue造成的死鎖苫拍,請(qǐng)關(guān)注你的onChanged代碼塊防止有異常出現(xiàn)破壞數(shù)據(jù)通知的邏輯

三個(gè)函數(shù)不是很復(fù)雜的系統(tǒng)函數(shù)實(shí)現(xiàn)了數(shù)據(jù)的防重復(fù)通知芜繁,連續(xù)多次通知只取最后一次的性能優(yōu)化,在數(shù)據(jù)非活動(dòng)狀態(tài)時(shí)不更新三個(gè)強(qiáng)大的LiveData的基礎(chǔ)功能绒极,這是我們?cè)陂_(kāi)發(fā)中設(shè)計(jì)代碼結(jié)構(gòu)的一個(gè)很好的學(xué)習(xí)案例骏令,比如可以應(yīng)用到用戶連續(xù)需點(diǎn)擊多次提交只取最后一次的業(yè)務(wù)需求中

歡迎關(guān)注Mike的簡(jiǎn)書(shū)

Android知識(shí)整理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市垄提,隨后出現(xiàn)的幾起案子榔袋,更是在濱河造成了極大的恐慌周拐,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摘昌,死亡現(xiàn)場(chǎng)離奇詭異速妖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)聪黎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)备恤,“玉大人稿饰,你說(shuō)我怎么就攤上這事÷恫矗” “怎么了喉镰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)惭笑。 經(jīng)常有香客問(wèn)我侣姆,道長(zhǎng),這世上最難降的妖魔是什么沉噩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任捺宗,我火速辦了婚禮,結(jié)果婚禮上川蒙,老公的妹妹穿的比我還像新娘蚜厉。我一直安慰自己,他們只是感情好畜眨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布昼牛。 她就那樣靜靜地躺著,像睡著了一般康聂。 火紅的嫁衣襯著肌膚如雪贰健。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天恬汁,我揣著相機(jī)與錄音伶椿,去河邊找鬼。 笑死蕊连,一個(gè)胖子當(dāng)著我的面吹牛悬垃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甘苍,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼尝蠕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了载庭?” 一聲冷哼從身側(cè)響起看彼,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廊佩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后靖榕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體标锄,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年茁计,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了料皇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡星压,死狀恐怖践剂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娜膘,我是刑警寧澤逊脯,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站竣贪,受9級(jí)特大地震影響军洼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜演怎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一匕争、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颤枪,春花似錦汗捡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盗胀,卻和暖如春艘蹋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背票灰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工女阀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屑迂。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓浸策,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惹盼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子庸汗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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