【譯】LiveData 使用詳解

前言

本文翻譯自【Understanding LiveData made simple】,詳細介紹了 liveData 的使用。感謝作者 Elye。水平有限,歡迎指正討論。
Architecture Components 可以說是 Google 提供給 Android 開發(fā)者的一大福利。LiveData 是其中的一個主要組件,下面我們一起看下該怎么使用好 LiveData
如果你之前沒了解過 Architecture Components,可以看下作者的另一篇文章:Android Architecture Components for Dummies in Kotlin (50 lines of code)
在上一篇文章中,作者提到了 ViewModelLiveData,其中 LiveData 是用來從 ViewModel 層向 View 層傳遞數(shù)據(jù)。但當時并沒有完整地介紹 LiveData 的作用,現(xiàn)在我們來詳細看下 LiveData 的定義和使用。
那么,LiveData 有什么特別的地方呢?

正文

什么是 LiveData

官方 定義是:

LiveData 是一個可被觀察的數(shù)據(jù)持有類。與普通的被觀察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命周期感知的,也就是說,它能感知其它應用組件(Activity,F(xiàn)ragment,Service)的生命周期。這種感知能力可以確保只有處于 active 狀態(tài)的組件才能收到 LiveData 的更新。詳情可查看 Lifecycle。

這就是官方對 LiveData 的定義。
為了簡單起見,我們先來看一些之前的開發(fā)習慣,以便更好地理解。

起源

當 Android 剛誕生的時候,大多數(shù)開發(fā)者寫的代碼都放在一個 Activity 中。


1-All-in-one-Activity.png

然而,把所有邏輯都放在一個 Activity 類中并不理想。因為 Activity 很難進行單元測試。
鑒于此,業(yè)界出現(xiàn)了MVC、MVP、MVVM 等開發(fā)架構,通過 Controller、Presenter、ViewModel 等分層抽離 Activity 中的代碼。


2-Presenter-ViewModel.png

這種架構能把邏輯從 View 層分離出來。然而,它的問題是 Controller、Presenter、ViewModel 等不能感知 Activity 的生命周期,Activity 的生命周期必須通知這些組件。
為了統(tǒng)一解決方案,Google 開始重視這個問題,于是 Architecture Components 誕生了。
其中 ViewModel 組件有一個特殊能力,我們不需要手動通知它們,就可以感知 Activity 的生命周期。這是系統(tǒng)內(nèi)部幫我們做的事情。


3-Lifecycle-LiveData-ViewModel.png

除了 ViewModel 外,用于從 ViewModel 層暴露到 View 層的數(shù)據(jù),也有生命周期感知的能力,這就是為什么叫做 LiveData 的原因。作為一個被觀察者,它可以感知觀察它的 Activity 的生命周期。

舉例說明

為了更好地理解,下圖將 LiveData 作為數(shù)據(jù)中心:


4-LiveData-Center.png

從上圖可以看到,LiveData 的數(shù)據(jù)來源一般是 ViewModel,或者其它用來更新 LiveData 的組件。一旦數(shù)據(jù)更新后,LiveData 就會通知它的所有觀察者,例如 Activity、Fragment、Service 等組件。但是,與其他類似 RxJava 的方法不同的是,LiveData 并不是盲目的通知所有觀察者,而是首先檢查它們的實時狀態(tài)。LiveData 只會通知處于 Actie 的觀察者,如果一個觀察者處于 PausedDestroyed 狀態(tài),它將不會受到通知。
這樣的好處是,我們不需要在 onPauseonDestroy 方法中解除對 LiveData 的訂閱/觀察。此外,一旦觀察者重新恢復 Resumed 狀態(tài),它將會重新收到 LiveData 的最新數(shù)據(jù)。

LiveData 的子類

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

MutableLiveData

MutableLiveData 是 LiveData 的一個最簡單實現(xiàn),它可以接收數(shù)據(jù)更新并通知觀察者。
例如:

// 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 數(shù)據(jù)發(fā)生變化(例如7567和6269),F(xiàn)ragment 就會收到更新。

5-MutableLiveData.gif

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

getLiveDataA().observe(this, changeObserver)

這就是訂閱 LiveData 的地方,但是并沒有在 Fragment pausingterminating 時解除訂閱。
即使我們沒有解除訂閱,也不會有什么問題。看下面的例子,當 Fragment 銷毀時,LiveData 不會因為產(chǎn)生一個新數(shù)據(jù)(1428)通知給 inactive 的 Fragment 而崩潰(Crash)。

6-MutableLiveData-inactive-Fragment.gif

同時,也可以看到當 Fragment 重新 active 時,將會收到最新的 LiveData 數(shù)據(jù):1428。

Transformations#map()

我們一般定義一個 Repository 負責從網(wǎng)絡或數(shù)據(jù)庫獲取數(shù)據(jù),在將這些數(shù)據(jù)傳遞到 View 層之前,可能需要做一些處理。
如下圖,我們使用 LiveData 在各個層之間傳遞數(shù)據(jù):

7-Transformations.Map-Repository-LiveData.png

我們可以使用 Transformations#map() 方法將數(shù)據(jù)從一個 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 的數(shù)據(jù)(5116)進行處理后變?yōu)?A:5116。

8-Transformations.Map-Sample.gif

使用 Transformations#map() 有助于確保 LiveData 的數(shù)據(jù)不會傳遞給處于 dead 狀態(tài)的 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() 方法,這個方法改變了數(shù)據(jù)內(nèi)容。
也就是說,我們可以通過 MediatorLiveData 將多個 LiveData 源數(shù)據(jù)集合起來,如下圖所示:

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 ..
}

這樣,F(xiàn)ragment 就可以同時接收到 LiveDataALiveDataB 的數(shù)據(jù)變化,如下圖所示:

11-MediatorLiveData-Sample.gif

有一點需要注意的是:當 Fragment 再處于 active 狀態(tài)時,如果 LiveDataALiveDataB 的數(shù)據(jù)都發(fā)生了變化,那么當 Fragment 重新恢復 active 狀態(tài)時,MediatorLiveData 將獲取最后添加的 LiveData 的數(shù)據(jù)發(fā)送給 Fragment,這里即 LiveDataB。

12-MediatorLiveData-Sample-2.gif

從上圖可以看到,當 Fragment 恢復活動狀態(tài)時,它就會收到 LiveDataB 的最新數(shù)據(jù),無論 LiveDataB 變化的比 LiveDataA 變化的早或晚。從上面代碼可以看到,這是因為 LiveDataB 是最后被添加到 MediatorLiveData 中的。

Transformations#switchMap

上面的示例中展示了我們可以同時監(jiān)聽兩個 LiveData 的數(shù)據(jù)變化,這是很有用的。但是,如果我們想要手動控制只監(jiān)聽其中一個的數(shù)據(jù)變化,并能根據(jù)需要隨時切換,這時應怎么辦呢?
答案是:Transformations#switchMap(),Google 已經(jīng)為我們提供了這個方法。它的定義如下:

@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;
}

這個方法用來添加一個新數(shù)據(jù)源并相應地刪除前一個數(shù)據(jù)源。因此 MediatorLiveData 只會包含一個 LiveData 數(shù)據(jù)源。這個控制開關也是一個 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 ..
}

這樣,我們就能很容易地控制用哪個數(shù)據(jù)來更新 View 視圖,如下所示,當正在觀察的 LiveData 發(fā)生變化,或者切換觀察的 LiveData 時,F(xiàn)ragment 都會收到數(shù)據(jù)更新。


14-Transformations.switchMap.png

一個實際的使用場景是,我們可以通過特定設置(如用戶登錄 session)的不同數(shù)據(jù)源,來處理不同的業(yè)務邏輯。

源碼地址

以上示例代碼可以在作者的 Github 上找到:https://github.com/elye/demo_android_livedata_illustration
下載源碼查看,能更好地理解。

更多示例

如果訂閱了一個 LiveData,但又不想收到數(shù)據(jù)更新的通知,可以參考一下文章:
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)。

參考

聯(lián)系

我是 xiaobailong24,您可以通過以下平臺找到我:

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容