前言
本文翻譯自【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 中。

然而,把所有邏輯都放在一個 Activity 類中並不理想。因為 Activity 很難進行單元測試。
鑑於此,業界出現了MVC、MVP、MVVM 等開發架構,通過 Controller、Presenter、ViewModel 等分層抽離 Activity 中的程式碼。

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

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

從上圖可以看到,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 就會收到更新。

上面的程式碼中有這麼一行:
getLiveDataA().observe(this, changeObserver)
複製程式碼
這就是訂閱 LiveData 的地方,但是並沒有在 Fragment pausing 或 terminating 時解除訂閱。
即使我們沒有解除訂閱,也不會有什麼問題。看下面的例子,當 Fragment 銷燬時,LiveData 不會因為產生一個新資料(1428)通知給 inactive 的 Fragment 而崩潰(Crash)。

同時,也可以看到當 Fragment 重新 active 時,將會收到最新的 LiveData 資料:1428。
Transformations#map()
我們一般定義一個 Repository 負責從網路或資料庫獲取資料,在將這些資料傳遞到 View 層之前,可能需要做一些處理。
如下圖,我們使用 LiveData 在各個層之間傳遞資料:

我們可以使用 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。

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

這很酷,我們不用擔心解除訂閱。
下面來看下 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 源資料集合起來,如下圖所示:

程式碼如下:
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
的資料變化,如下圖所示:

有一點需要注意的是:當 Fragment 不 再處於 active
狀態時,如果 LiveDataA
和 LiveDataB
的資料都發生了變化,那麼當 Fragment 重新恢復 active 狀態時,MediatorLiveData
將獲取最後新增的 LiveData 的資料傳送給 Fragment,這裡即 LiveDataB
。

從上圖可以看到,當 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。整個過程如下所示:

使用方法如下:
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 都會收到資料更新。

一個實際的使用場景是,我們可以通過特定設定(如使用者登入 session)的不同資料來源,來處理不同的業務邏輯。
原始碼地址
以上示例程式碼可以在作者的 Github 上找到:github.com/elye/demo_a…。
下載原始碼檢視,能更好地理解。
更多示例
如果訂閱了一個 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,您可以通過以下平臺找到我: