Android官方架構(gòu)組件LiveData: 觀察者模式領(lǐng)域二三事

本文是 《Android Jetpack 官方架構(gòu)組件》 系列文章, LiveData本身很簡單,但其代表卻正是 MVVM 模式最重要的思想,即 數(shù)據(jù)驅(qū)動視圖(也有叫觀察者模式、響應(yīng)式等)——這也是擺脫 順序性編程思維 的重要一步哪工。

回顧LiveData:從處境尷尬到咸魚翻身

我們都知道Google在去年的 I/O 大會非常隆重地推出了一系列的 架構(gòu)組件赂弓,本文的主角涉枫,LiveData 正是其中之一探越,和Lifecycle狡赐、ViewModelRoom比較起來钦幔,LiveData可以說是最受關(guān)注的組件也不為過枕屉,遺憾的是,在發(fā)布的最初鲤氢,關(guān)注點是因為它飽含爭議搀擂,相當一部分的開發(fā)者認為——LiveData 實在太 雞肋 了!

2017年的 Android 技術(shù)領(lǐng)域卷玉,RxJava無疑是炙手可熱的名詞之一哨颂,其 觀察者模式鏈式調(diào)用 所表現(xiàn)出來的 API 優(yōu)秀地設(shè)計,使得它位于很多 Android項目技術(shù)選型中的 第一序列相种。

這時 Google 隆重推出了具有類似功能的 LiveData (其本質(zhì)就是觀察者模式)威恼,可以說是有點初生牛犢不怕虎的感覺,開發(fā)者們不由自主將LiveDataRxJava 進行了對比寝并,結(jié)論基本出奇的一致—— LiveData所提供的功能箫措,RxJava完全足以勝任,而后者卻同時具有龐大的生態(tài)圈衬潦,這是LiveData短時間內(nèi)難以撼動(替代)的斤蔓。

時至今日,LiveData的使用者越來越多镀岛,最主要的原因當然和Google的強力支持不無關(guān)系附迷,但是LiveData本身優(yōu)秀的設(shè)計和輕量級也吸引了越來越多開發(fā)者的青睞。

現(xiàn)在我們需要去了解它了哎媚,我們都知道喇伯,LiveData 本質(zhì)是 觀察者模式 的體現(xiàn),可關(guān)鍵的問題是:

觀察者模式到底是啥拨与?稻据!

討論這個問題之前,我們先看看 LiveData 的用法买喧,這實在沒什么技術(shù)難度捻悯,比如,你可以這樣實例化一個LiveData并使用它:

為了保證代碼簡潔可讀淤毛,示例代碼我使用了Kotlin

如你所見今缚,LiveData實際上就像一個 容器, 本文中它存儲了一個String類型的引用,每當這個容器內(nèi) String的數(shù)據(jù)發(fā)生變化低淡,我們都能在回調(diào)函數(shù)中進行對應(yīng)的處理姓言,比如 Toast瞬项。

這似乎和我們?nèi)粘S玫降?Button 控件的 setOnClickListener() 非常相似,實際上點擊事件的監(jiān)聽也正是 觀察者模式 的一種體現(xiàn)何荚,對于觀察者來說囱淋,它并不關(guān)心觀察對象 數(shù)據(jù)是如何過來的,而只關(guān)心數(shù)據(jù)過來后 進行怎樣的處理餐塘。

這也就是說妥衣,事件發(fā)射的上游接收事件的下游 互不干涉,大幅降低了互相持有的依賴關(guān)系所帶來的強耦合性戒傻。

我依然堅持學(xué)習(xí)原理比學(xué)習(xí)如何應(yīng)用的優(yōu)先級更高税手,因此我們先來一一探究LiveData本身設(shè)計中存在的那些閃光點背后的故事。

LiveData是如何避免內(nèi)存泄漏的

我們都知道需纳,RxJava在使用過程中芦倒,避免內(nèi)存泄漏是一個不可忽視的問題,因此我們一般需要借助三方庫比如RxLifecycle候齿、AutoDispose來解決這個問題。

而反觀LiveData闺属,當它被我們的Activity訂閱觀察,這之后Activity如果finish()掉,LiveData本身會自動“清理”以避免內(nèi)存泄漏征唬。

這是一個非常好用的特性丢烘,它的實現(xiàn)原理非常簡單,其本質(zhì)就是利用了Jetpack 架構(gòu)組件中的另外一個成員—— Lifecycle国瓮。

讓我們來看看LiveData被訂閱時內(nèi)部的代碼:

源碼中的邏輯非常復(fù)雜灭必,我們只關(guān)注核心代碼:

  • 1.首先我們在調(diào)用LiveData.observer()方法時,傳遞的第一個參數(shù)Acitivity實際被向上抽象成為了 LifecycleOwner乃摹,第二個參數(shù)Obserser實際就是我們的觀察后的回調(diào)禁漓。

這里我們需要注意的是,執(zhí)行LiveData.observer()方法時 必須處于主線程孵睬,否則會因為斷言失敗而拋出異常播歼。

  • 2.方法內(nèi)部實際上將我們傳入的2個參數(shù)包裝成了一個新的 LifecycleBoundObserver對象,它實現(xiàn)了 Lifecycle 組件中的LifecycleObserver接口:

這里就解釋了為什么LiveData能夠 自動解除訂閱而避免內(nèi)存泄漏 了掰读,因為它內(nèi)部能夠感應(yīng)到Activity或者Fragment的生命周期秘狞。

這種設(shè)計非常巧妙——在我們初識 Lifecycle 組件時,總是下意識認為它能夠?qū)Υ蟮膶ο筮M行有效生命周期的管理(比如 Presenter)蹈集,實際上烁试,這種生命周期的管理我們完全可以應(yīng)用到各個功能的基礎(chǔ)組件中,比如大到吃內(nèi)存的 MediaPlayer(多媒體播放器)拢肆、繪制設(shè)計復(fù)雜的 自定義View减响,小到隨處可見的LiveData靖诗,都可以通過實現(xiàn)LifecycleObserver接口達到 感應(yīng)生命周期并內(nèi)部釋放重的資源 的目的。

關(guān)于上述代碼中注釋了 更新LiveData的活躍狀態(tài) 的源碼辩蛋,我們先跳過呻畸,稍后我們會詳細探討它。

    1. 我們繼續(xù)回到上上一個源碼片段的第三步中悼院,對于一個可觀察的LiveData來講伤为,當然存在多個觀察者同時訂閱觀察的情況,因此考慮到這一點据途,Google的工程師們?yōu)槊恳粋€LiveData配置了一個Map存儲所有的觀察者绞愚。
  • 4.到了這一步,我們將第2步包裝生成的對象交給我們傳入的 Activity颖医,讓它在不同的生命周期事件中去逐一通知其所有的觀察者位衩,當然也包含了我們的LiveData

數(shù)據(jù)更新后如何通知到回調(diào)方法熔萧?

LiveData原生的API提供了2種方式供開發(fā)者更新數(shù)據(jù), 分別是 setValue()postValue()糖驴,官方文檔明確標明:setValue()方法必須在 主線程 進行調(diào)用,而postValue()方法更適合在執(zhí)行較重工作 子線程 中進行調(diào)用(比如網(wǎng)絡(luò)請求等)——在所有情況下佛致,調(diào)用setValue()postValue()都會 觸發(fā)觀察者并更新UI贮缕。

柿子挑軟的捏,我們先看setValue()方法的實現(xiàn)原理:

通過保留最終的核心代碼俺榆,我們很清晰了解了setValue()方法為什么能更新LiveData的值感昼,并且通知到回調(diào)函數(shù)中的代碼去執(zhí)行,比如更新UI罐脊。

但是我們知道定嗓,普遍情況下,Android不允許在子線程更新UI萍桌,但是postValue()方法卻可以在子線程更新LiveData()的數(shù)據(jù)宵溅,并通知更新UI,這是如何實現(xiàn)的呢上炎?

其實答案已經(jīng)呼之欲出了层玲,就是通過 Handler

現(xiàn)在你已經(jīng)對LiveData整體了一個基本的了解了,接下來讓我們開始去探究更細節(jié)的閃光點反症。

看完源碼辛块,你告訴我才算入門?

LiveData本身非常簡單铅碍,畢竟它本身的源碼一共也就500行左右润绵,也許你要說 準備面試粗讀一遍源碼就夠了,很遺憾胞谈,即使是粗讀了源碼尘盼,也很難說能夠完全招架更深入的提問...

讓我們來看一道題目:在下述Activity完整的生命周期中憨愉,Activity一共觀察到了幾次數(shù)據(jù)的變更——即 一共打印了幾條Log ?(補充糾正卿捎,onStop()方法中值應(yīng)該為 "onStop")

公布答案:

意外的是配紫,livedata.observer()的本次觀察并沒有觀察到 onCreateonStoponDestroy 的數(shù)據(jù)變更午阵。

為什么會這樣躺孝?

還記得上文提到過2次的 LiveData的活躍狀態(tài)(Active) 相關(guān)代碼嗎?實際上底桂,LiveData內(nèi)部存儲的每一個LifecycleBoundObserver本身都有shouldBeActive的狀態(tài):

現(xiàn)在我們明白了植袍,原來并不是只要在onDestroy()之前為LiveData進行更新操作,LiveData的觀察者就能響應(yīng)到對應(yīng)的事件的籽懦。

雖然我們明白了這一點于个,但是如果更深入的思考,你會又多一個問題暮顺,那就是:

  • 既然LiveData已經(jīng)能夠?qū)崿F(xiàn)在onDestroy()的生命周期時自動解除訂閱厅篓,為什么還要多此一舉設(shè)置一個Active的狀態(tài)呢?

仔細想想捶码,其實也不難得到答案羽氮,Activity并非只有onDestroy()一種狀態(tài)的,更多時候宙项,新的Activity運行在棧頂乏苦,舊的Activity就會運行在 background——這時舊的Activity會執(zhí)行對應(yīng)的onPause()onStop()方法株扛,我們當然不會關(guān)心運行在后臺的Activity所觀察的LiveData對象(即使數(shù)據(jù)更新了尤筐,我們也無從進行對應(yīng)UI的更新操作),因此LiveData進入 InActive(待定洞就、非活躍)狀態(tài)盆繁,return并且不去執(zhí)行對應(yīng)的回調(diào)方法,是 非逞縝密的優(yōu)秀設(shè)計 油昂。

當然,有同學(xué)提出倾贰,我如果希望這種情況下冕碟,Activity在后臺依然能夠響應(yīng)數(shù)據(jù)的變更,可不可以呢匆浙?當然可以安寺,LiveData此外還提供了observerForever()方法,在這種情況下首尼,它能夠響應(yīng)到任何生命周期中數(shù)據(jù)的變更事件:

除此之外挑庶,源碼中處處都是優(yōu)秀的細節(jié)言秸,比如對于observe()方法和observerForever()方法對應(yīng)生成的包裝類,后者方法生成的是AlwaysActiveObserver對象迎捺,統(tǒng)一抽象為ObserverWrapper举畸。

這種即使只有2種不同場景,也通過代碼的設(shè)計凳枝,將公共業(yè)務(wù)進行向上抽離為抽象類的嚴謹抄沮,也非常值得我們學(xué)習(xí)。

小結(jié)范舀,與更深入的思考

本來寫了更多合是,篇幅所限,最終還是決定刪除了相當一部分和 RxJava 有關(guān)的內(nèi)容锭环,這些內(nèi)容并非是將 LiveDataRxJava 進行對比一決高下—— 例如聪全,Google官方提供了 LiveDataRxJava 互相進行轉(zhuǎn)換的工具類:

https://developer.android.com/reference/android/arch/lifecycle/LiveDataReactiveStreams

值得玩味的是,官方的工具類中辅辩,LiveDataRxJava的轉(zhuǎn)換方法难礼,返回值并非是一個Flowable,而是一個Publisher接口:

正如我在注釋中標注的玫锋,這個工具方法返回的是一個接口蛾茉,很大程度上限制了我們對RxJava眾多強大操作符的使用,這是否是來自Google的惡意撩鹿?

當然不是谦炬,對于這種行為,我的理解是Google對于LiveData本身嚴格的約束——它只應(yīng)該用于進行數(shù)據(jù)的觀察节沦,而不是花哨的操作键思;轉(zhuǎn)換為Flowable當然非常簡單,但是這種行為是否屬于LiveData本身職責(zé)的逾越甫贯,更準確來說吼鳞,是否屬于不必要的過度設(shè)計?這些是我們需要去細細揣度的叫搁。

我無從驗證我的理解是否正確赔桌,但是我的這個理由已經(jīng)足夠說服我自己,再往下已不再是LiveData的范疇渴逻,關(guān)于這一點我將會專門起一篇文章去進行更深入的探討疾党,歡迎關(guān)注。

--------------------------廣告分割線------------------------------

系列文章

爭取打造 Android Jetpack 講解的最好的博客系列

Android Jetpack 實戰(zhàn)篇


關(guān)于我

Hello惨奕,我是卻把清梅嗅雪位,如果您覺得文章對您有價值,歡迎 ??墓贿,也歡迎關(guān)注我的個人博客或者Github茧泪。

如果您覺得文章還差了那么點東西蜓氨,也請通過關(guān)注督促我寫出更好的文章——萬一哪天我進步了呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末队伟,一起剝皮案震驚了整個濱河市穴吹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗜侮,老刑警劉巖港令,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異锈颗,居然都是意外死亡顷霹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門击吱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淋淀,“玉大人,你說我怎么就攤上這事覆醇《浞祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵永脓,是天一觀的道長袍辞。 經(jīng)常有香客問我,道長常摧,這世上最難降的妖魔是什么搅吁? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮落午,結(jié)果婚禮上谎懦,老公的妹妹穿的比我還像新娘。我一直安慰自己板甘,他們只是感情好党瓮,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布详炬。 她就那樣靜靜地躺著盐类,像睡著了一般。 火紅的嫁衣襯著肌膚如雪呛谜。 梳的紋絲不亂的頭發(fā)上在跳,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音隐岛,去河邊找鬼猫妙。 笑死,一個胖子當著我的面吹牛聚凹,可吹牛的內(nèi)容都是我干的割坠。 我是一名探鬼主播齐帚,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彼哼!你這毒婦竟也來了对妄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤敢朱,失蹤者是張志新(化名)和其女友劉穎剪菱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拴签,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡孝常,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚓哩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片构灸。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岸梨,靈堂內(nèi)的尸體忽然破棺而出冻押,到底是詐尸還是另有隱情,我是刑警寧澤盛嘿,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布洛巢,位于F島的核電站,受9級特大地震影響次兆,放射性物質(zhì)發(fā)生泄漏稿茉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一芥炭、第九天 我趴在偏房一處隱蔽的房頂上張望漓库。 院中可真熱鬧,春花似錦园蝠、人聲如沸渺蒿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茂装。三九已至,卻和暖如春善延,著一層夾襖步出監(jiān)牢的瞬間少态,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工易遣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留彼妻,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像侨歉,于是被迫代替她去往敵國和親屋摇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容