淺談 LiveData 的通知機制

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

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

一、一個 LiveData 的問題

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

LiveData的問題

有兩個頁面 A 和 B,A 是一個 Fragment ,是一個列表的展現頁;B 是其餘的頁面。首先,A 會更新頁面,而且爲了防止連續更新,再每次更新以前須要檢查一個布爾值,只有爲 false 的時候才容許從網絡加載數據。每次加載數據以前會將該布爾值置爲 true,拿到告終果以後置爲 false. 這裏拿到的結果是藉助 LiveData 來通知給頁面進行更新的。github

如今,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. 因此,咱們後來對數據的修改沒有體現到列表上面。架構

二、問題的緣由

若是用上面的示例代碼做爲例子,那麼出現問題的緣由就是當 A 處於後臺的時候。雖然調用了 loadData() 而且從網絡中拿到了數據,可是調用 data.setValue() 方法的時候沒法通知到 A 中。因此,loading = false 這一行沒法被調用到。第二次發出通知的時候,同樣調用到了 loadData(),可是由於此時 loading 爲 true,因此並無執行加載數據的操做。而當從 B 中徹底回到 A 的時候,第一次加載的數據被 A 接收到。因此,列表中的數據是第一次加載時的數據,第二次加載事件丟失了。app

解決這個問題的方法固然比較簡單,能夠當接收到事件的時候使用布爾變量監聽,而後回到頁面的時候發現數據發生變化再執行數據加載:ide

// 類 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() 方法,從而消息沒法被傳遞出去。

三、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

相關文章
相關標籤/搜索