【譯】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ù)據再姑。但當時并沒有完整地介紹 LiveData 的作用,現(xiàn)在我們來詳細看下 LiveData 的定義和使用找御。
那么元镀,LiveData 有什么特別的地方呢?

正文

什么是 LiveData

官方 定義是:

LiveData 是一個可被觀察的數(shù)據持有類霎桅。與普通的被觀察者(如 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)內部幫我們做的事情沃斤。


3-Lifecycle-LiveData-ViewModel.png

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

舉例說明

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


4-LiveData-Center.png

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

LiveData 的子類

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

MutableLiveData

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

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

5-MutableLiveData.gif

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

getLiveDataA().observe(this, changeObserver)

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

6-MutableLiveData-inactive-Fragment.gif

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

Transformations#map()

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

7-Transformations.Map-Repository-LiveData.png

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

8-Transformations.Map-Sample.gif

使用 Transformations#map() 有助于確保 LiveData 的數(shù)據不會傳遞給處于 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ù)據內容盛杰。
也就是說挽荡,我們可以通過 MediatorLiveData 將多個 LiveData 源數(shù)據集合起來,如下圖所示:

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ù)據變化,如下圖所示:

11-MediatorLiveData-Sample.gif

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

12-MediatorLiveData-Sample-2.gif

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

Transformations#switchMap

上面的示例中展示了我們可以同時監(jiān)聽兩個 LiveData 的數(shù)據變化祈远,這是很有用的呆万。但是,如果我們想要手動控制只監(jiān)聽其中一個的數(shù)據變化车份,并能根據需要隨時切換谋减,這時應怎么辦呢?
答案是: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;
}

這個方法用來添加一個新數(shù)據源并相應地刪除前一個數(shù)據源庄吼。因此 MediatorLiveData 只會包含一個 LiveData 數(shù)據源。這個控制開關也是一個 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ù)據來更新 View 視圖,如下所示梢为,當正在觀察的 LiveData 發(fā)生變化渐行,或者切換觀察的 LiveData 時,F(xiàn)ragment 都會收到數(shù)據更新抖誉。


14-Transformations.switchMap.png

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

源碼地址

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

更多示例

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

參考

聯(lián)系

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市郁副,隨后出現(xiàn)的幾起案子减牺,更是在濱河造成了極大的恐慌,老刑警劉巖存谎,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拔疚,死亡現(xiàn)場離奇詭異,居然都是意外死亡既荚,警方通過查閱死者的電腦和手機稚失,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恰聘,“玉大人句各,你說我怎么就攤上這事∏邕叮” “怎么了凿宾?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兼蕊。 經常有香客問我菌湃,道長,這世上最難降的妖魔是什么遍略? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任惧所,我火速辦了婚禮,結果婚禮上绪杏,老公的妹妹穿的比我還像新娘下愈。我一直安慰自己,他們只是感情好蕾久,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布势似。 她就那樣靜靜地躺著,像睡著了一般僧著。 火紅的嫁衣襯著肌膚如雪履因。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天盹愚,我揣著相機與錄音栅迄,去河邊找鬼。 笑死皆怕,一個胖子當著我的面吹牛毅舆,可吹牛的內容都是我干的。 我是一名探鬼主播愈腾,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼憋活,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了虱黄?” 一聲冷哼從身側響起悦即,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橱乱,沒想到半個月后辜梳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡仅醇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年冗美,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片析二。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡粉洼,死狀恐怖,靈堂內的尸體忽然破棺而出叶摄,到底是詐尸還是另有隱情属韧,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布蛤吓,位于F島的核電站宵喂,受9級特大地震影響,放射性物質發(fā)生泄漏会傲。R本人自食惡果不足惜锅棕,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一拙泽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裸燎,春花似錦顾瞻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至移稳,卻和暖如春蕴纳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背个粱。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工古毛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人几蜻。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓喇潘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梭稚。 傳聞我的和親對象是個殘疾皇子颖低,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內容