【譯】LiveData 使用詳解
前言
本文翻譯自【 ofollow,noindex">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) 。
在上一篇文章中,作者提到了 ViewModel
和 LiveData
,其中 LiveData 是用來從 ViewModel 層向 View 層傳遞資料。但當時並沒有完整地介紹 LiveData 的作用,現在我們來詳細看下 LiveData 的定義和使用。
那麼,LiveData 有什麼特別的地方呢?
正文
什麼是 LiveData
官方 定義是:
LiveData 是一個可被觀察的資料持有類。與普通的被觀察者(如 RxJava 中的 Observable)不同的是,LiveData 是生命週期感知的,也就是說,它能感知其它應用元件(Activity,Fragment,Service)的生命週期。這種感知能力可以確保只有處於 active 狀態的元件才能收到 LiveData 的更新。詳情可檢視 Lifecycle 。
這就是官方對 LiveData 的定義。
為了簡單起見,我們先來看一些之前的開發習慣,以便更好地理解。
起源
當 Android 剛誕生的時候,大多數開發者寫的程式碼都放在一個 Activity 中。

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 的生命週期。這是系統內部幫我們做的事情。

3-Lifecycle-LiveData-ViewModel.png
除了 ViewModel 外,用於從 ViewModel 層暴露到 View 層的資料,也有生命週期感知的能力,這就是為什麼叫做 LiveData
的原因。作為一個被觀察者,它可以感知觀察它的 Activity 的生命週期。
舉例說明
為了更好地理解,下圖將 LiveData 作為資料中心:

4-LiveData-Center.png
從上圖可以看到,LiveData 的資料來源一般是 ViewModel,或者其它用來更新 LiveData 的元件。一旦資料更新後,LiveData 就會通知它的所有觀察者,例如 Activity、Fragment、Service 等元件。但是,與其他類似 RxJava 的方法不同的是,LiveData 並不是盲目的通知所有觀察者,而是首先檢查它們的實時狀態。LiveData 只會通知處於 Actie 的觀察者,如果一個觀察者處於 Paused 或 Destroyed 狀態,它將不會受到通知。
這樣的好處是,我們不需要在 onPause
或 onDestroy
方法中解除對 LiveData 的訂閱/觀察。此外,一旦觀察者重新恢復 Resumed 狀態,它將會重新收到 LiveData 的最新資料。
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 pausing 或 terminating 時解除訂閱。
即使我們沒有解除訂閱,也不會有什麼問題。看下面的例子,當 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 就可以同時接收到 LiveDataA
和 LiveDataB
的資料變化,如下圖所示:

11-MediatorLiveData-Sample.gif
有一點需要注意的是:當 Fragment 不 再處於 active
狀態時,如果 LiveDataA
和 LiveDataB
的資料都發生了變化,那麼當 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 上找到: https://github.com/elye/demo_android_livedata_illustration 。
下載原始碼檢視,能更好地理解。
更多示例
如果訂閱了一個 LiveData,但又不想收到資料更新的通知,可以參考一下文章:
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case) 。
參考
- Understanding LiveData made simple
- LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
- LiveData
- MediatorLiveData
- MutableLiveData
- Transformations
聯絡
我是 xiaobailong24 ,您可以通過以下平臺找到我: