本文是 《Android Jetpack 官方架構(gòu)組件》 系列文章, LiveData
本身很簡單,但其代表卻正是 MVVM 模式最重要的思想,即 數(shù)據(jù)驅(qū)動視圖(也有叫觀察者模式、響應(yīng)式等)——這也是擺脫 順序性編程思維 的重要一步哪工。
回顧LiveData:從處境尷尬到咸魚翻身
我們都知道Google在去年的 I/O 大會非常隆重地推出了一系列的 架構(gòu)組件赂弓,本文的主角涉枫,LiveData 正是其中之一探越,和Lifecycle
狡赐、ViewModel
、Room
比較起來钦幔,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ā)者們不由自主將LiveData
和 RxJava
進行了對比寝并,結(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
并使用它:
如你所見今缚,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) 的源碼辩蛋,我們先跳過呻畸,稍后我們會詳細探討它。
- 我們繼續(xù)回到上上一個源碼片段的第三步中悼院,對于一個可觀察的
LiveData
來講伤为,當然存在多個觀察者同時訂閱觀察的情況,因此考慮到這一點据途,Google的工程師們?yōu)槊恳粋€LiveData
配置了一個Map
存儲所有的觀察者绞愚。
- 我們繼續(xù)回到上上一個源碼片段的第三步中悼院,對于一個可觀察的
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()
的本次觀察并沒有觀察到 onCreate、onStop 和 onDestroy 的數(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)容并非是將 LiveData
和 RxJava
進行對比一決高下—— 例如聪全,Google官方提供了 LiveData
和 RxJava
互相進行轉(zhuǎn)換的工具類:
https://developer.android.com/reference/android/arch/lifecycle/LiveDataReactiveStreams
值得玩味的是,官方的工具類中辅辩,LiveData
向RxJava
的轉(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官方架構(gòu)組件Lifecycle:生命周期組件詳解&原理分析
- Android官方架構(gòu)組件ViewModel:從前世今生到追本溯源
- Android官方架構(gòu)組件LiveData: 觀察者模式領(lǐng)域二三事
- Android官方架構(gòu)組件Paging:分頁庫的設(shè)計美學(xué)
- Android官方架構(gòu)組件Paging-Ex:為分頁列表添加Header和Footer
- Android官方架構(gòu)組件Paging-Ex:列表狀態(tài)的響應(yīng)式管理
- Android官方架構(gòu)組件Navigation:大巧不工的Fragment管理框架
- Android官方架構(gòu)組件DataBinding-Ex:雙向綁定篇
Android Jetpack 實戰(zhàn)篇:
關(guān)于我
Hello惨奕,我是卻把清梅嗅雪位,如果您覺得文章對您有價值,歡迎 ??墓贿,也歡迎關(guān)注我的個人博客或者Github茧泪。
如果您覺得文章還差了那么點東西蜓氨,也請通過關(guān)注督促我寫出更好的文章——萬一哪天我進步了呢?