淺談 LiveData 的通知機制

LiveData 和 ViewModel 是 Google 官方的 MVVM 架構(gòu)的一個組成部分桐筏。巧了,昨天分析了一個問題是 ViewModel 的生命周期導(dǎo)致的逛拱。今天又遇到了一個問題是 LiveData 通知導(dǎo)致的柏锄。而 ViewModel 的生命周期和 LiveData 的通知機制是它們的主要責(zé)任。所以眶痰,就這個機會我們也來分析一下 LiveData 通知的實現(xiàn)過程。

  1. 關(guān)于 ViewModel 的生命周期:《淺談 LiveData 的通知機制》;
  2. 關(guān)于 MVVM 設(shè)計模式的基本應(yīng)用梯啤,你可以參考這篇文章:《Android 架構(gòu)設(shè)計:MVC、MVP存哲、MVVM和組件化》.

1因宇、一個 LiveData 的問題

今天所遇到的問題是這樣的,

LiveData的問題

有兩個頁面 A 和 B祟偷,A 是一個 Fragment 察滑,是一個列表的展示頁;B 是其他的頁面修肠。首先贺辰,A 會更新頁面,并且為了防止連續(xù)更新,再每次更新之前需要檢查一個布爾值饲化,只有為 false 的時候才允許從網(wǎng)絡(luò)加載數(shù)據(jù)莽鸭。每次加載數(shù)據(jù)之前會將該布爾值置為 true,拿到了結(jié)果之后置為 false. 這里拿到的結(jié)果是借助 LiveData 來通知給頁面進行更新的吃靠。

現(xiàn)在硫眨,A 打開了 B,B 中對列表中的數(shù)據(jù)進行了更新巢块,然后發(fā)了一條類似于廣播的消息礁阁。此時,A 接收了消息并進行數(shù)據(jù)加載族奢。過了一段時間姥闭,B 準(zhǔn)備退出,再退出的時候又對列表中的項目進行了更新越走,所以此時又發(fā)出了一條消息棚品。

B 關(guān)閉了,我們回到了 A 頁面弥姻。但是南片,此時,我們發(fā)現(xiàn) A 頁面中的數(shù)據(jù)只包含了第一次的數(shù)據(jù)更新庭敦,第二次的數(shù)據(jù)更新沒有體現(xiàn)在列表中疼进。

用代碼來描述的話大致是下面這樣,

    // 類 A
    public class A extends Fragment {
    
        private boolean loading = false;

        private MyViewModel vm;

        // ......

        /**
         * Register load observer.
         */
        public void registerObservers() {
            vm.getData().observe(this, resources -> {
                loading = false;
                // ... show in list
            })
        }

        /**
         * Load data from server.
         */
        public void loadData() {
            if (loading) return;
            loading = true;
            vm.load();
        }

        /**
         * On receive message.
         */
        public void onReceive() {
            loadData();
        }
    }

    public class B extends Activity {

        public void doBusiness1() {
            sendMessage(MSG); // Send message when on foreground.
        }

        @Override
        public void onBackpressed() {
            // ....
            sendMessage(MSG); // Send message when back
        }
    }

    public class MyViewModel extends ViewModel {

        private MutableLiveData<Resoucres<Object>> data;

        public MutableLiveData<Resoucres<Object>> getData() {
            if (data == null) {
                data = new MutableLiveData<>();
            }
            return data;
        }

        public void load() {
            Object result = AsyncGetData.getData(); // Get data
            if (data != null) {
                data.setValue(Resouces.success(result));
            }
        }
    }

A 打開了 B 之后秧廉,A 處于后臺伞广,B 處于前臺。此時疼电,B 調(diào)用 doBusiness1() 發(fā)送了一條消息 MSG嚼锄,A 中在 onReceive() 中收到消息,并調(diào)用 loadData() 加載數(shù)據(jù)蔽豺。然后区丑,B 處理完了業(yè)務(wù),準(zhǔn)備退出的時候發(fā)現(xiàn)其他數(shù)據(jù)發(fā)生了變化修陡,所以又發(fā)了一條消息沧侥,然后 onReceive() 中收到消息,并調(diào)用 loadData(). 但此時發(fā)現(xiàn) loading 為 true. 所以魄鸦,我們后來對數(shù)據(jù)的修改沒有體現(xiàn)到列表上面宴杀。

2、問題的原因

如果用上面的示例代碼作為例子拾因,那么出現(xiàn)問題的原因就是當(dāng) A 處于后臺的時候旺罢。雖然調(diào)用了 loadData() 并且從網(wǎng)絡(luò)中拿到了數(shù)據(jù)旷余,但是調(diào)用 data.setValue() 方法的時候無法通知到 A 中。所以扁达,loading = false 這一行無法被調(diào)用到正卧。第二次發(fā)出通知的時候,一樣調(diào)用到了 loadData()罩驻,但是因為此時 loading 為 true穗酥,所以并沒有執(zhí)行加載數(shù)據(jù)的操作。而當(dāng)從 B 中完全回到 A 的時候惠遏,第一次加載的數(shù)據(jù)被 A 接收到砾跃。所以,列表中的數(shù)據(jù)是第一次加載時的數(shù)據(jù)节吮,第二次加載事件丟失了抽高。

解決這個問題的方法當(dāng)然比較簡單,可以當(dāng)接收到事件的時候使用布爾變量監(jiān)聽透绩,然后回到頁面的時候發(fā)現(xiàn)數(shù)據(jù)發(fā)生變化再執(zhí)行數(shù)據(jù)加載:

    // 類 A
    public class A extends Fragment {
    
        private boolean dataChanged;

        /**
         * On receive message.
         */
        public void onReceive() {
            dataChanged = true;
        }

        @Override
        public void onResume() {
            // ...
            if (dataChanged) {
                loadData();
            }
        }
    }

對于上面的問題翘骂,當(dāng)我們調(diào)用了 setValue() 之后將調(diào)用到 LiveData 類的 setValue() 方法,

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

這里表明該方法必須在主線程中被調(diào)用帚豪,最終事件的分發(fā)將會交給 dispatchingValue() 方法來執(zhí)行:

    private void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    // 發(fā)送事件
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

然后碳竟,會調(diào)用 considerNotify() 方法來最終將事件傳遞出去,

    private void considerNotify(ObserverWrapper observer) {
        // 這里會因為當(dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出方法
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

這里會因為當(dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出 considerNotify() 方法狸臣,從而消息無法被傳遞出去莹桅。

3、LiveData 的通知機制

LiveData 的通知機制并不復(fù)雜烛亦,它的類主要包含在 livedata-core 包下面诈泼,總共也就 3 個類。LiveData 是一個抽象類煤禽,它有一個默認(rèn)的實現(xiàn)就是 MutableLiveData.

LiveData 主要依靠內(nèi)部的變量 mObservers 來緩存訂閱的對象和訂閱信息铐达。其定義如下,使用了一個哈希表進行緩存和映射檬果,

private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>();

每當(dāng)我們調(diào)用一次 observe() 方法的時候就會有一個映射關(guān)系被加入到哈希表中瓮孙,

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // 持有者當(dāng)前處于被銷毀狀態(tài),因此可以忽略此次觀察
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

從上面的代碼我們可以看出选脊,添加到映射關(guān)系中的類會先被包裝成 LifecycleBoundObserver 對象杭抠。然后使用該對象對 owner 的生命周期進行監(jiān)聽。

這的 LifecycleBoundObserverObserverWrapper 兩個類的定義如下知牌,

    class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
        @NonNull final LifecycleOwner mOwner;

        LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
            super(observer);
            mOwner = owner;
        }

        @Override
        boolean shouldBeActive() {
            return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
        }

        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

        @Override
        boolean isAttachedTo(LifecycleOwner owner) {
            return mOwner == owner;
        }

        @Override
        void detachObserver() {
            mOwner.getLifecycle().removeObserver(this);
        }
    }

    private abstract class ObserverWrapper {
        final Observer<T> mObserver;
        boolean mActive;
        int mLastVersion = START_VERSION;

        ObserverWrapper(Observer<T> observer) {
            mObserver = observer;
        }

        abstract boolean shouldBeActive();

        boolean isAttachedTo(LifecycleOwner owner) {
            return false;
        }

        void detachObserver() {}

        void activeStateChanged(boolean newActive) {
            if (newActive == mActive) {
                return;
            }
            mActive = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += mActive ? 1 : -1;
            if (wasInactive && mActive) {
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !mActive) {
                onInactive();
            }
            if (mActive) {
                dispatchingValue(this);
            }
        }
    }

上面的類中我們先來關(guān)注 LifecycleBoundObserver 中的 onStateChanged() 方法。該方法繼承自 LifecycleObserver. 這里的 Lifecycle.Event 是一個枚舉類型斤程,定義了一些與生命周期相關(guān)的枚舉值角寸。所以菩混,當(dāng) Activity 或者 Fragment 的生命周期發(fā)生變化的時候會回調(diào)這個方法。從上面我們也可以看出扁藕,該方法內(nèi)部又調(diào)用了基類的 activeStateChanged() 方法沮峡,該方法主要用來更新當(dāng)前的 Observer 是否處于 Active 的狀態(tài)。我們上面無法通知也是因為在這個方法中 mActive 被置為 false 造成的亿柑。

繼續(xù)看 activeStateChanged() 方法邢疙,我們可以看出在最后的幾行中,它調(diào)用了 dispatchingValue(this) 方法望薄。所以疟游,當(dāng) Fragment 從處于后臺切換到前臺之后,會將當(dāng)前緩存的值通知給觀察者痕支。

那么值是如何緩存的颁虐,以及緩存了多少值呢?回到之前的 setValue()dispatchingValue() 方法中卧须,我們發(fā)現(xiàn)值是以一個單獨的變量進行緩存的另绩,

    private volatile Object mData = NOT_SET;

因此,在我們的示例中花嘶,當(dāng)頁面從后臺切換到前臺的時候笋籽,只能將最后一次緩存的結(jié)果通知給觀察者就真相大白了。

總結(jié)

從上面的分析中椭员,我們對 LiveData 總結(jié)如下车海,

  1. 當(dāng)調(diào)用 observe() 方法的時候,我們的觀察者將會和 LifecycleOwner (Fragment 或者 Activity) 一起被包裝到一個類中拆撼,并使用哈希表建立映射關(guān)系容劳。同時,還會對 Fragment 或者 Activity 的生命周期方法進行監(jiān)聽闸度,依次來達到監(jiān)聽觀察者是否處于 active 狀態(tài)的目的竭贩。
  2. 當(dāng) Fragment 或者 Activity 處于后臺的時候,其內(nèi)部的觀察者將處于非 active 狀態(tài)莺禁,此時使用 setValue() 設(shè)置的值會緩存到 LiveData 中留量。但是這種緩存只能緩存一個值,新的值會替換舊的值哟冬。因此楼熄,當(dāng)頁面從后臺恢復(fù)到前臺的時候只有最后設(shè)置的一個值會被傳遞給觀察者。
  3. 2 中的當(dāng) Fragment 或者 Activity 從后臺恢復(fù)的時候進行通知也是通過監(jiān)聽其生命周期方法實現(xiàn)的浩峡。
  4. 調(diào)用了 observe() 之后可岂,F(xiàn)ragment 或者 Activity 被緩存了起來,不會造成內(nèi)存泄漏嗎翰灾?答案是不會的缕粹。因為 LiveData 可以對其生命周期進行監(jiān)聽稚茅,當(dāng)其處于銷毀狀態(tài)的時候,該映射關(guān)系將被從緩存中移除平斩。

以上亚享。

(如有疑問,可以在評論中交流)


如果你喜歡這篇文章绘面,請點贊欺税!你也可以在以下平臺關(guān)注我:

所有的文章維護在:Github, Android-notes

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市揭璃,隨后出現(xiàn)的幾起案子晚凿,更是在濱河造成了極大的恐慌,老刑警劉巖塘辅,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晃虫,死亡現(xiàn)場離奇詭異,居然都是意外死亡扣墩,警方通過查閱死者的電腦和手機哲银,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呻惕,“玉大人荆责,你說我怎么就攤上這事⊙谴啵” “怎么了做院?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長濒持。 經(jīng)常有香客問我键耕,道長,這世上最難降的妖魔是什么柑营? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任屈雄,我火速辦了婚禮,結(jié)果婚禮上官套,老公的妹妹穿的比我還像新娘酒奶。我一直安慰自己,他們只是感情好奶赔,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布惋嚎。 她就那樣靜靜地躺著,像睡著了一般站刑。 火紅的嫁衣襯著肌膚如雪另伍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天绞旅,我揣著相機與錄音摆尝,去河邊找鬼愕宋。 笑死,一個胖子當(dāng)著我的面吹牛结榄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播囤捻,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼臼朗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝎土?” 一聲冷哼從身側(cè)響起视哑,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎誊涯,沒想到半個月后挡毅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡暴构,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年跪呈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片取逾。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡耗绿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出砾隅,到底是詐尸還是另有隱情误阻,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布晴埂,位于F島的核電站究反,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏儒洛。R本人自食惡果不足惜精耐,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晶丘。 院中可真熱鬧黍氮,春花似錦、人聲如沸浅浮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滚秩。三九已至专执,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郁油,已是汗流浹背本股。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工攀痊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拄显。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓苟径,卻偏偏與公主長得像,于是被迫代替她去往敵國和親躬审。 傳聞我的和親對象是個殘疾皇子棘街,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

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