【譯】LiveData 使用詳解

前言

本文翻譯自【Understanding LiveData made simple】,詳細介紹了 liveData 的使用。感謝做者 Elye。水平有限,歡迎指正討論。 Architecture Components 能夠說是 Google 提供給 Android 開發者的一大福利。LiveData 是其中的一個主要組件,下面咱們一塊兒看下該怎麼使用好 LiveData。 若是你以前沒了解過 Architecture Components,能夠看下做者的另外一篇文章:Android Architecture Components for Dummies in Kotlin (50 lines of code)。 在上一篇文章中,做者提到了 ViewModelLiveData,其中 LiveData 是用來從 ViewModel 層向 View 層傳遞數據。但當時並無完整地介紹 LiveData 的做用,如今咱們來詳細看下 LiveData 的定義和使用。 那麼,LiveData 有什麼特別的地方呢?android

正文

什麼是 LiveData

官方 定義是:git

LiveData 是一個可被觀察的數據持有類。與普通的被觀察者(如 RxJava 中的 Observable)不一樣的是,LiveData 是生命週期感知的,也就是說,它能感知其它應用組件(Activity,Fragment,Service)的生命週期。這種感知能力能夠確保只有處於 active 狀態的組件才能收到 LiveData 的更新。詳情可查看 Lifecyclegithub

這就是官方對 LiveData 的定義。 爲了簡單起見,咱們先來看一些以前的開發習慣,以便更好地理解。數據庫

起源

當 Android 剛誕生的時候,大多數開發者寫的代碼都放在一個 Activity 中。 bash

1-All-in-one-Activity.png

然而,把全部邏輯都放在一個 Activity 類中並不理想。由於 Activity 很難進行單元測試。 鑑於此,業界出現了MVC、MVP、MVVM 等開發架構,經過 Controller、Presenter、ViewModel 等分層抽離 Activity 中的代碼。 網絡

2-Presenter-ViewModel.png

這種架構能把邏輯從 View 層分離出來。然而,它的問題是 Controller、Presenter、ViewModel 等不能感知 Activity 的生命週期,Activity 的生命週期必須通知這些組件。 爲了統一解決方案,Google 開始重視這個問題,因而 Architecture Components 誕生了。 其中 ViewModel 組件有一個特殊能力,咱們不須要手動通知它們,就能夠感知 Activity 的生命週期。這是系統內部幫咱們作的事情。 session

3-Lifecycle-LiveData-ViewModel.png

除了 ViewModel 外,用於從 ViewModel 層暴露到 View 層的數據,也有生命週期感知的能力,這就是爲何叫作 LiveData 的緣由。做爲一個被觀察者,它能夠感知觀察它的 Activity 的生命週期。架構

舉例說明

爲了更好地理解,下圖將 LiveData 做爲數據中心: app

4-LiveData-Center.png

從上圖能夠看到,LiveData 的數據來源通常是 ViewModel,或者其它用來更新 LiveData 的組件。一旦數據更新後,LiveData 就會通知它的全部觀察者,例如 Activity、Fragment、Service 等組件。可是,與其餘相似 RxJava 的方法不一樣的是,LiveData 並非盲目的通知全部觀察者,而是首先檢查它們的實時狀態。LiveData 只會通知處於 Actie 的觀察者,若是一個觀察者處於 PausedDestroyed 狀態,它將不會受到通知。 這樣的好處是,咱們不須要在 onPauseonDestroy 方法中解除對 LiveData 的訂閱/觀察。此外,一旦觀察者從新恢復 Resumed 狀態,它將會從新收到 LiveData 的最新數據。ide

LiveData 的子類

LiveData 是一個抽象類,咱們不能直接使用。幸運的是,Google 提供了一些其簡單實現,讓咱們來使用。

MutableLiveData

MutableLiveData 是 LiveData 的一個最簡單實現,它能夠接收數據更新並通知觀察者。 例如:

// Declaring it
val liveDataA = MutableLiveData<String>()

// Trigger the value change
liveDataA.value = someValue

// Optionally, one could use liveDataA.postValue(value)
// to get it set on the UI thread
複製代碼

觀察 LiveData 也很簡單,下面展現了在 Fragment 中訂閱 LiveDataA

class MutableLiveDataFragment : Fragment() {

    private val changeObserver = Observer<String> { value ->
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        getLiveDataA().observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
複製代碼

結果以下,一旦 LiveDataA 數據發生變化(例如7567和6269),Fragment 就會收到更新。

5-MutableLiveData.gif

上面的代碼中有這麼一行:

getLiveDataA().observe(this, changeObserver)
複製代碼

這就是訂閱 LiveData 的地方,可是並無在 Fragment pausingterminating 時解除訂閱。 即便咱們沒有解除訂閱,也不會有什麼問題。看下面的例子,當 Fragment 銷燬時,LiveData 不會由於產生一個新數據(1428)通知給 inactive 的 Fragment 而崩潰(Crash)。

6-MutableLiveData-inactive-Fragment.gif

同時,也能夠看到當 Fragment 從新 active 時,將會收到最新的 LiveData 數據:1428。

Transformations#map()

咱們通常定義一個 Repository 負責從網絡或數據庫獲取數據,在將這些數據傳遞到 View 層以前,可能須要作一些處理。 以下圖,咱們使用 LiveData 在各個層之間傳遞數據:

7-Transformations.Map-Repository-LiveData.png

咱們能夠使用 Transformations#map() 方法將數據從一個 LiveData 傳遞到另外一個 LiveData。

class TransformationMapFragment : Fragment() {

    private val changeObserver = Observer<String> { value ->
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        val transformedLiveData = Transformations.map(
                getLiveDataA()) { "A:$it" }
        transformedLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
複製代碼

結果以下所示,以上代碼將 LiveDataA 的數據(5116)進行處理後變爲 A:5116

8-Transformations.Map-Sample.gif

使用 Transformations#map() 有助於確保 LiveData 的數據不會傳遞給處於 dead 狀態的 ViewModel 和 View。

9-Transformations.Map-Helpful.png

這很酷,咱們不用擔憂解除訂閱。 下面來看下 Transformations#map() 的源碼:

@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
        @NonNull final Function<X, Y> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(func.apply(x));
        }
    });
    return result;
}
複製代碼

這裏用到了 LiveData 的另外一個子類 MediatorLiveData。接下來看一看這是個什麼東西。

MediatorLiveData

Transformations#map() 源碼中能夠看到,MediatorLiveData 有一個 MediatorLiveData#addSource() 方法,這個方法改變了數據內容。 也就是說,咱們能夠經過 MediatorLiveData 將多個 LiveData 源數據集合起來,以下圖所示:

10-MediatorLiveData.png

代碼以下:

class MediatorLiveDataFragment : Fragment() {

    private val changeObserver = Observer<String> { value ->
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        val mediatorLiveData = MediatorLiveData<String>()
        mediatorLiveData.addSource(getliveDataA())
              { mediatorLiveData.value = "A:$it" }
        mediatorLiveData.addSource(getliveDataB())
              { mediatorLiveData.value = "B:$it" }
        mediatorLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
複製代碼

這樣,Fragment 就能夠同時接收到 LiveDataALiveDataB 的數據變化,以下圖所示:

11-MediatorLiveData-Sample.gif

有一點須要注意的是:當 Fragment 再處於 active 狀態時,若是 LiveDataALiveDataB 的數據都發生了變化,那麼當 Fragment 從新恢復 active 狀態時,MediatorLiveData 將獲取最後添加的 LiveData 的數據發送給 Fragment,這裏即 LiveDataB

12-MediatorLiveData-Sample-2.gif

從上圖能夠看到,當 Fragment 恢復活動狀態時,它就會收到 LiveDataB 的最新數據,不管 LiveDataB 變化的比 LiveDataA 變化的早或晚。從上面代碼能夠看到,這是由於 LiveDataB 是最後被添加到 MediatorLiveData 中的。

Transformations#switchMap

上面的示例中展現了咱們能夠同時監聽兩個 LiveData 的數據變化,這是頗有用的。可是,若是咱們想要手動控制只監聽其中一個的數據變化,並能根據須要隨時切換,這時應怎麼辦呢? 答案是:Transformations#switchMap(),Google 已經爲咱們提供了這個方法。它的定義以下:

@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
        @NonNull final Function<X, LiveData<Y>> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(trigger, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            LiveData<Y> newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                result.addSource(mSource, new Observer<Y>() {
                    @Override
                    public void onChanged(@Nullable Y y) {
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}
複製代碼

這個方法用來添加一個新數據源並相應地刪除前一個數據源。所以 MediatorLiveData 只會包含一個 LiveData 數據源。這個控制開關也是一個 LiveData。整個過程以下所示:

13-Transformations.switchMap-Sample.gif

使用方法以下:

class TransformationSwitchMapFragment : Fragment() {

    private val changeObserver = Observer<String> { value ->
        value?.let { txt_fragment.text = it }
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)

        val transformSwitchedLiveData =
            Transformations.switchMap(getLiveDataSwitch()) {
                switchToB ->
                if (switchToB) {
                     getLiveDataB()
                } else {
                     getLiveDataA()
                }
        }

        transformSwitchedLiveData.observe(this, changeObserver)
    }
    // .. some other Fragment specific code ..
}
複製代碼

這樣,咱們就能很容易地控制用哪一個數據來更新 View 視圖,以下所示,當正在觀察的 LiveData 發生變化,或者切換觀察的 LiveData 時,Fragment 都會收到數據更新。

14-Transformations.switchMap.png

一個實際的使用場景是,咱們能夠經過特定設置(如用戶登陸 session)的不一樣數據源,來處理不一樣的業務邏輯。

源碼地址

以上示例代碼能夠在做者的 Github 上找到:github.com/elye/demo_a…。 下載源碼查看,能更好地理解。

更多示例

若是訂閱了一個 LiveData,但又不想收到數據更新的通知,能夠參考一下文章: LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

參考

聯繫

我是 xiaobailong24,您能夠經過如下平臺找到我:

相關文章
相關標籤/搜索