前言
本文翻譯自【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)。
在上一篇文章中,作者提到了 ViewModel 和 LiveData,其中 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 中。

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

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

除了 ViewModel 外,用于從 ViewModel 層暴露到 View 層的數(shù)據(jù),也有生命周期感知的能力,這就是為什么叫做 LiveData 的原因。作為一個被觀察者,它可以感知觀察它的 Activity 的生命周期。
舉例說明
為了更好地理解,下圖將 LiveData 作為數(shù)據(jù)中心:

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

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

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

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

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

代碼如下:
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 就可以同時接收到 LiveDataA 和 LiveDataB 的數(shù)據(jù)變化,如下圖所示:

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

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

使用方法如下:
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ù)更新。

一個實際的使用場景是,我們可以通過特定設置(如用戶登錄 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)。
參考
- Understanding LiveData made simple
- LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
- LiveData
- MediatorLiveData
- MutableLiveData
- Transformations
聯(lián)系
我是 xiaobailong24,您可以通過以下平臺找到我: