Android Jetpack LiveData原理分析

??????網上關于DataBinding,ViewModel,LiveData文章很多,最近結合源碼及相關實例分析了一下,本文結合LiveData的使用來對LiveData進行源碼分析。
??????關于DataBinding的使用,可以參考之前的文章:
??????Android JetPack DataBinding分析

一.LiveData簡介

??????LiveData 是一個可觀察的數據持有者類。與常規 observable 不同,LiveData 是生命周期感知的,這意味著它尊重其他應用程序組件的生命周期,例如 activity,fragment。此感知確保 LiveData僅更新處于活動狀態的應用組件觀察者。
??????如果 Observer 類表示的觀察者生命周期處于 STARTEDRESUMED 狀態,則 LiveData 會將其視為活動狀態。LiveData 僅通知處于活動狀態的觀察者更新信息。非活動狀態的觀察者不會收到有關數據更改的通知。
??????你可以注冊與實現了 LifecycleOwner 接口的對象配對的觀察者。此關系允許在相應 Lifecycle 對象的狀態更改為 DESTROYED 時刪除觀察者。這對于 activity 和 fragment 特別有用,因為它們可以安全地觀察 LiveData 對象而不用擔心泄漏 - activity 和 fragment 在其生命周期被銷毀時立即取消訂閱。

二.LiveData使用

??????LiveData是一個抽象類,不能直接實例話,google為我們定義好了實現類MutableLiveData,可以通過MutableLiveData來創建不同類型的對象,結合代碼來看一下:

a.基礎功能
private MutableLiveData<String> mLiveData = new MutableLiveData<>();
mLiveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(String s) {
        Toast.makeText(HookActivity.this, "receive message is " + s,Toast.LENGTH_SHORT).show();
    }
});
mLiveData.setValue("test lifecycle observe");

??????通過以上可以看到,使用起來還是比較簡單的,先創建一個MutableLiveData對象,執行observe()方法,傳入當前UI實現類(A/F)的對象引用和observer(監聽data變化),執行setValue()來更新MutableLiveData的值。

b.進階功能

??????UI內實現了對某個LiveData的監聽,當UI處于后臺后,此時LiveData的值進行了更新,那應該如何處理呢?結合代碼來看一下:

private void testDataObserverLifecycle() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                mLiveData.postValue("test lifecycle observe");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

??????以上方法可以看到,是啟動一個線程,然后在3s后對LiveData進行了值更新,注意一下,postValue()是在非UI線程執行的值更新調用方法,當我們在執行以上方法后,將當前界面切換到后臺,3s之后再切換回來,發現會彈出toast提示。
??????結論:LiveData會監聽UI的生命周期變化,當處于前臺后,會將值變化通知到UI。

c.全局功能

??????通過以上可以看到,當LiveData發生變化后,UI處于后臺時,是不會收到值更新的,但是當返回前臺后,會立刻收到值變化通知UI,那如果LiveData作為一個全局的變量,新的UI(A/F)啟動后監聽該LiveData會怎樣呢?結合代碼來看一下:
①.定義全局變量
??????定義一個單例模式的類,里面定義一個HashMap來存儲MutableLiveData,通過key來獲取,如果map中存在,就返回;否則創建,然后存入map。

public final class LiveDataBus {

    private final Map<String, MutableLiveData<Object>> bus;

    private static class InstanceHolder {
        public static LiveDataBus sInstance = new LiveDataBus();
    }

    private LiveDataBus() {
        bus = new HashMap<>();
    }

    public static synchronized LiveDataBus getInstance() {
        return InstanceHolder.sInstance;
    }

    public <T> MutableLiveData<T> with(String target, Class<T> type) {
        if (!bus.containsKey(target)) {
            bus.put(target, new MutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(target);
    }
}

②.創建LiveData
??????先在Activity1內使用對應key的變量,并更新值:

private void testData() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                LiveDataBus.getInstance().with("key_test", String.class).postValue("LiveDataBus test");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

③.使用LiveData
??????后在類Activity2內使用對應key的變量:

LiveDataBus.getInstance().with("key_test", String.class).observe(this,
        new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Toast.makeText(Activity2.this, "receive message is " + s,Toast.LENGTH_SHORT).show();
            }
        });

??????我們發現,會彈出Toast提示,也就是說后面創建新的UI內部使用該LiveData,也會收到回調,跟stick broadcast類似,后面注冊該broadcast時,會立刻收到廣播。
??????在對LiveData進行使用后,有了一定的了解,帶著問題去看一下LiveData的源碼。

三.LiveData源碼分析

a.observe()

image.png

??????LiveData注冊了LifecycleOwner(Activity/Fragment)生命周期的觀察者,當Activity/Fragment生命周期發生變化后,LiveData會對生命周期狀態進行判斷,來確定是否需要通知LifecycleOwner進行邏輯更新,詳情可參考Android Jetpack Lifecycle詳解,注冊邏輯如下:

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    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);
}

??????從上面可以看到:在進行observe()時,首先確保是主線程,如果LifeCycleOwner的生命周期是destroyed時,直接返回;然后將owner及observer封裝成LifecycleBoundObserver,添加到mObservers里面,后續值發生變化時,會遍歷回調;最后將wrapper添加到owner的lifecycle生命周期的觀察者。


image.png

??????接下來看一下LifecycleBoundObserver的實現:

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    .......

    @Override
    //判斷owner是否處于active狀態
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }

    @Override
    //LifecycleEventObserver實現了LifecycleObserver,owner生命周期狀態發生變化后的回調
    public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        //當owner生命周期狀態發生變化后,看是否需要將數據更新到owner,如果從后臺切換到前臺后,會將最新數據更新到UI[每次執行setValue()后,都會保存最新的mData = value]。
        activeStateChanged(shouldBeActive());
    }
    ......
}

??????LifecycleBoundObserver實現了LifecycleEventObserver,owner有生命周期變化后,會回調onStateChanged()方法,當其生命周期變為DESTROYED后,會移除observer,這也就是為什么我們不需要手動 remove observer 的原因,此后LiveData發生變化后,不會更新到Activity/Fragment,即不會一直持有其引用,從而不會造成內存泄露;
??????當Activity/Fragment處于前臺后,會將最新的數據更新到UI,邏輯如下:

void activeStateChanged(boolean newActive) {
    ......
    if (mActive) {
        dispatchingValue(this);
    }
}

??????感知數據變化如下:


image.png
b.setValue()/postValue()
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

protected void postValue(T value) {
    ......
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

??????執行setValue()及postValue()后,會先更新mData為最新的value,然后執行dispatchingValue()。

c.dispatchingValue()
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<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

??????通過以上可以看到,在執行dispatchingValue()后,會對參數進行判斷,如果不為空,會單獨調用considerNotify();如果為空,則會遍歷調用considerNotify()。

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

??????通過以上可以看到,在considerNotify()內部會最終調用observer.mObserver的onChanged()方法。

d.通知途徑

??????以上可以看到,有兩種途徑會最終調用dispatchingValue():
??????1.setValue():調用dispatchingValue(null),會通知所有的observer,進而再調用considerNotify()進行更新。
??????2.A/F(LifeCycleOwner)的activeStateChanged():調用dispatchingValue(this),由于此處參數不為null,所以會單獨回調監聽該owner對應的observer。LiveData內部關聯了LifecycleOwner(Activity/Fragment)生命周期相關的方法,當LifecycleOwner生命周期變化的時候都會回調onStateChanged()方法,然后會去調用activeStateChanged(),最后調用considerNotify()方法去執行數據變化回調。
??????接收事件流程:


image.png

??????以上就是對LiveData源碼的分析,包括observe()及其內部封裝、setValue()后執行dispatchValue()、considerNotify()最終執行observer的onChanged()進行通知及數據更新。

四.LiveData粘性去除

??????以上實例可以看到,LiveData可以作為全局變量來使用,具有粘性的功能,如果不需要粘性功能,需要怎么處理呢?先看一下considerNotify()方法的內部實現:

private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    observer.mObserver.onChanged((T) mData);
}

??????如果修改內部執行邏輯的話,可以通過hook來實現,內部有3處可以執行return,但是唯一可以hook的就是來使mLastVersion==mVersion,來使條件滿足執行return,看一下實現方式:

public final class LiveDataBus {
    ......
    public <T> MutableLiveData<T> with(String target, Class<T> type) {
        if (!bus.containsKey(target)) {
            bus.put(target, new BusMutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(target);
    }

    private static class BusMutableLiveData<T> extends MutableLiveData<T> {

        @Override
        public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
            super.observe(owner, observer);
            try {
                //通過設置mLastVersion = mVersion在執行considerNotify()時返回,消除粘性
                hook(observer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void hook(@NonNull Observer<?> observer) {
            Class<?> liveDataClass = LiveData.class;
            try {
                Field mObserversField = liveDataClass.getDeclaredField("mObservers");
                mObserversField.setAccessible(true);
                Object mObservers = mObserversField.get(this);
                Class<?> mObserversClass = mObservers.getClass();

                Method getMethod = mObserversClass.getDeclaredMethod("get", Object.class);
                getMethod.setAccessible(true);
                Object entry = getMethod.invoke(mObservers, observer);
                Object observerWrapper = ((Map.Entry) entry).getValue();
                Class<?> observerClass = observerWrapper.getClass().getSuperclass();

                Field mLastVersionField = observerClass.getDeclaredField("mLastVersion");
                mLastVersionField.setAccessible(true);
                Field mVersionField = liveDataClass.getDeclaredField("mVersion");
                mVersionField.setAccessible(true);
                Object mVersionValue = mVersionField.get(this);
                mLastVersionField.set(observerWrapper, mVersionValue);
            } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

??????以上可以看到,自定義類繼承MutableLiveData類,在observe()后,執行hook(),將observer的mLastVersion的值設成跟mVersion一致就可以了。

五.LiveData 的優點:

??????a.確保你的 UI 符合你的數據狀態
??????LiveData遵循觀察者模式,狀態更改時LiveData會通知 Observer對象,觀察者可以在每次數據變化時更新UI。
??????b.沒有內存泄漏
??????觀察者綁定到 Lifecycle 對象,并在其相關生命周期被銷毀后自行清理。
??????c.不會因為 activity 停止而發生崩潰
??????如果觀察者的生命周期處于非活動狀態(例如,activity 在后臺堆棧中),則它不會接收任何 LiveData 事件。
??????d.不再需要手動處理生命周期
??????UI 組件只是觀察相關數據,不會停止或恢復觀察。LiveData自動管理所有這些,因為它在觀察時意識到相關的生命周期狀態變化。
??????e.始終保持最新數據
??????如果生命周期變為非活動狀態,它將在再次變為活動狀態時接收最新數據。例如,后臺 activity 在返回前臺后立即接收最新數據。
??????f.適當的配置更改
??????如果由于配置更改(例如設備旋轉)而重新創建 activity 或 fragment,則會立即接收最新的可用數據。
??????g.共享資源
??????可以使用單例模式擴展 LiveData 對象以包裝系統服務,以便可以在應用程序中共享它們。LiveData 對象連接到系統服務一次,然后任何需要該資源的觀察者只需觀察 LiveData 對象。
??????以上是對LiveData的使用及源碼分析,詳細理解需要重點關注LiveData這個類。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容