淺談 LiveData 的通知機制

WngShhng發表於2019-01-17

LiveData 和 ViewModel 是 Google 官方的 MVVM 架構的一個組成部分。巧了,昨天分析了一個問題是 ViewModel 的生命週期導致的。今天又遇到了一個問題是 LiveData 通知導致的。而 ViewModel 的生命週期和 LiveData 的通知機制是它們的主要責任。所以,就這個機會我們也來分析一下 LiveData 通知的實現過程。

  1. 關於 ViewModel 的生命週期:《淺談 LiveData 的通知機制》;
  2. 關於 MVVM 設計模式的基本應用,你可以參考這篇文章:《Android 架構設計:MVC、MVP、MVVM和元件化》.

1、一個 LiveData 的問題

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

LiveData的問題

有兩個頁面 A 和 B,A 是一個 Fragment ,是一個列表的展示頁;B 是其他的頁面。首先,A 會更新頁面,並且為了防止連續更新,再每次更新之前需要檢查一個布林值,只有為 false 的時候才允許從網路載入資料。每次載入資料之前會將該布林值置為 true,拿到了結果之後置為 false. 這裡拿到的結果是藉助 LiveData 來通知給頁面進行更新的。

現在,A 開啟了 B,B 中對列表中的資料進行了更新,然後發了一條類似於廣播的訊息。此時,A 接收了訊息並進行資料載入。過了一段時間,B 準備退出,再退出的時候又對列表中的專案進行了更新,所以此時又發出了一條訊息。

B 關閉了,我們回到了 A 頁面。但是,此時,我們發現 A 頁面中的資料只包含了第一次的資料更新,第二次的資料更新沒有體現在列表中。

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

    // 類 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 呼叫 doBusiness1() 傳送了一條訊息 MSG,A 中在 onReceive() 中收到訊息,並呼叫 loadData() 載入資料。然後,B 處理完了業務,準備退出的時候發現其他資料發生了變化,所以又發了一條訊息,然後 onReceive() 中收到訊息,並呼叫 loadData(). 但此時發現 loading 為 true. 所以,我們後來對資料的修改沒有體現到列表上面。

2、問題的原因

如果用上面的示例程式碼作為例子,那麼出現問題的原因就是當 A 處於後臺的時候。雖然呼叫了 loadData() 並且從網路中拿到了資料,但是呼叫 data.setValue() 方法的時候無法通知到 A 中。所以,loading = false 這一行無法被呼叫到。第二次發出通知的時候,一樣呼叫到了 loadData(),但是因為此時 loading 為 true,所以並沒有執行載入資料的操作。而當從 B 中完全回到 A 的時候,第一次載入的資料被 A 接收到。所以,列表中的資料是第一次載入時的資料,第二次載入事件丟失了。

解決這個問題的方法當然比較簡單,可以當接收到事件的時候使用布林變數監聽,然後回到頁面的時候發現資料發生變化再執行資料載入:

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

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

        @Override
        public void onResume() {
            // ...
            if (dataChanged) {
                loadData();
            }
        }
    }
複製程式碼

對於上面的問題,當我們呼叫了 setValue() 之後將呼叫到 LiveData 類的 setValue() 方法,

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

這裡表明該方法必須在主執行緒中被呼叫,最終事件的分發將會交給 dispatchingValue() 方法來執行:

    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(); ) {
                    // 傳送事件
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }
複製程式碼

然後,會呼叫 considerNotify() 方法來最終將事件傳遞出去,

    private void considerNotify(ObserverWrapper observer) {
        // 這裡會因為當前的 Fragment 沒有處於 active 狀態而退出方法
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }
複製程式碼

這裡會因為當前的 Fragment 沒有處於 active 狀態而退出 considerNotify() 方法,從而訊息無法被傳遞出去。

3、LiveData 的通知機制

LiveData 的通知機制並不複雜,它的類主要包含在 livedata-core 包下面,總共也就 3 個類。LiveData 是一個抽象類,它有一個預設的實現就是 MutableLiveData.

LiveData 主要依靠內部的變數 mObservers 來快取訂閱的物件和訂閱資訊。其定義如下,使用了一個雜湊表進行快取和對映,

private SafeIterableMap<Observer<T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
複製程式碼

每當我們呼叫一次 observe() 方法的時候就會有一個對映關係被加入到雜湊表中,

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // 持有者當前處於被銷燬狀態,因此可以忽略此次觀察
            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);
    }
複製程式碼

從上面的程式碼我們可以看出,新增到對映關係中的類會先被包裝成 LifecycleBoundObserver 物件。然後使用該物件對 owner 的生命週期進行監聽。

這的 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);
            }
        }
    }
複製程式碼

上面的類中我們先來關注 LifecycleBoundObserver 中的 onStateChanged() 方法。該方法繼承自 LifecycleObserver. 這裡的 Lifecycle.Event 是一個列舉型別,定義了一些與生命週期相關的列舉值。所以,當 Activity 或者 Fragment 的生命週期發生變化的時候會回撥這個方法。從上面我們也可以看出,該方法內部又呼叫了基類的 activeStateChanged() 方法,該方法主要用來更新當前的 Observer 是否處於 Active 的狀態。我們上面無法通知也是因為在這個方法中 mActive 被置為 false 造成的。

繼續看 activeStateChanged() 方法,我們可以看出在最後的幾行中,它呼叫了 dispatchingValue(this) 方法。所以,當 Fragment 從處於後臺切換到前臺之後,會將當前快取的值通知給觀察者。

那麼值是如何快取的,以及快取了多少值呢?回到之前的 setValue()dispatchingValue() 方法中,我們發現值是以一個單獨的變數進行快取的,

    private volatile Object mData = NOT_SET;
複製程式碼

因此,在我們的示例中,當頁面從後臺切換到前臺的時候,只能將最後一次快取的結果通知給觀察者就真相大白了。

總結

從上面的分析中,我們對 LiveData 總結如下,

  1. 當呼叫 observe() 方法的時候,我們的觀察者將會和 LifecycleOwner (Fragment 或者 Activity) 一起被包裝到一個類中,並使用雜湊表建立對映關係。同時,還會對 Fragment 或者 Activity 的生命週期方法進行監聽,依次來達到監聽觀察者是否處於 active 狀態的目的。
  2. 當 Fragment 或者 Activity 處於後臺的時候,其內部的觀察者將處於非 active 狀態,此時使用 setValue() 設定的值會快取到 LiveData 中。但是這種快取只能快取一個值,新的值會替換舊的值。因此,當頁面從後臺恢復到前臺的時候只有最後設定的一個值會被傳遞給觀察者。
  3. 2 中的當 Fragment 或者 Activity 從後臺恢復的時候進行通知也是通過監聽其生命週期方法實現的。
  4. 呼叫了 observe() 之後,Fragment 或者 Activity 被快取了起來,不會造成記憶體洩漏嗎?答案是不會的。因為 LiveData 可以對其生命週期進行監聽,當其處於銷燬狀態的時候,該對映關係將被從快取中移除。

以上。

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


如果你喜歡這篇文章,請點贊!你也可以在以下平臺關注我:

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

相關文章