淺談 LiveData 的通知機(jī)制

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

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

1、一個(gè) LiveData 的問題

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

LiveData的問題

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

現(xiàn)在,A 打開了 B,B 中對(duì)列表中的數(shù)據(jù)進(jìn)行了更新,然后發(fā)了一條類似于廣播的消息。此時(shí),A 接收了消息并進(jìn)行數(shù)據(jù)加載。過了一段時(shí)間,B 準(zhǔn)備退出,再退出的時(shí)候又對(duì)列表中的項(xiàng)目進(jìn)行了更新,所以此時(shí)又發(fā)出了一條消息。

B 關(guān)閉了,我們回到了 A 頁面。但是,此時(shí),我們發(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 處于后臺(tái),B 處于前臺(tái)。此時(shí),B 調(diào)用 doBusiness1() 發(fā)送了一條消息 MSG,A 中在 onReceive() 中收到消息,并調(diào)用 loadData() 加載數(shù)據(jù)。然后,B 處理完了業(yè)務(wù),準(zhǔn)備退出的時(shí)候發(fā)現(xiàn)其他數(shù)據(jù)發(fā)生了變化,所以又發(fā)了一條消息,然后 onReceive() 中收到消息,并調(diào)用 loadData(). 但此時(shí)發(fā)現(xiàn) loading 為 true. 所以,我們后來對(duì)數(shù)據(jù)的修改沒有體現(xiàn)到列表上面。

2、問題的原因

如果用上面的示例代碼作為例子,那么出現(xiàn)問題的原因就是當(dāng) A 處于后臺(tái)的時(shí)候。雖然調(diào)用了 loadData() 并且從網(wǎng)絡(luò)中拿到了數(shù)據(jù),但是調(diào)用 data.setValue() 方法的時(shí)候無法通知到 A 中。所以,loading = false 這一行無法被調(diào)用到。第二次發(fā)出通知的時(shí)候,一樣調(diào)用到了 loadData(),但是因?yàn)榇藭r(shí) loading 為 true,所以并沒有執(zhí)行加載數(shù)據(jù)的操作。而當(dāng)從 B 中完全回到 A 的時(shí)候,第一次加載的數(shù)據(jù)被 A 接收到。所以,列表中的數(shù)據(jù)是第一次加載時(shí)的數(shù)據(jù),第二次加載事件丟失了。

解決這個(gè)問題的方法當(dāng)然比較簡單,可以當(dāng)接收到事件的時(shí)候使用布爾變量監(jiān)聽,然后回到頁面的時(shí)候發(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();
            }
        }
    }

對(duì)于上面的問題,當(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ā)將會(huì)交給 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;
    }

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

    private void considerNotify(ObserverWrapper observer) {
        // 這里會(huì)因?yàn)楫?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);
    }

這里會(huì)因?yàn)楫?dāng)前的 Fragment 沒有處于 active 狀態(tài)而退出 considerNotify() 方法,從而消息無法被傳遞出去。

3、LiveData 的通知機(jī)制

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

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

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

每當(dāng)我們調(diào)用一次 observe() 方法的時(shí)候就會(huì)有一個(gè)映射關(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)系中的類會(huì)先被包裝成 LifecycleBoundObserver 對(duì)象。然后使用該對(duì)象對(duì) owner 的生命周期進(jìn)行監(jiān)聽。

這的 LifecycleBoundObserverObserverWrapper 兩個(gè)類的定義如下,

    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 是一個(gè)枚舉類型,定義了一些與生命周期相關(guān)的枚舉值。所以,當(dāng) Activity 或者 Fragment 的生命周期發(fā)生變化的時(shí)候會(huì)回調(diào)這個(gè)方法。從上面我們也可以看出,該方法內(nèi)部又調(diào)用了基類的 activeStateChanged() 方法,該方法主要用來更新當(dāng)前的 Observer 是否處于 Active 的狀態(tài)。我們上面無法通知也是因?yàn)樵谶@個(gè)方法中 mActive 被置為 false 造成的。

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

那么值是如何緩存的,以及緩存了多少值呢?回到之前的 setValue()dispatchingValue() 方法中,我們發(fā)現(xiàn)值是以一個(gè)單獨(dú)的變量進(jìn)行緩存的,

    private volatile Object mData = NOT_SET;

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

總結(jié)

從上面的分析中,我們對(duì) LiveData 總結(jié)如下,

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

以上。

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


如果你喜歡這篇文章,請(qǐng)點(diǎn)贊!你也可以在以下平臺(tái)關(guān)注我:

所有的文章維護(hù)在:Github, Android-notes

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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