本文是 《Android Jetpack 官方架構元件》 系列文章, LiveData
本身很簡單,但其代表卻正是 MVVM 模式最重要的思想,即 資料驅動檢視(也有叫觀察者模式、響應式等)——這也是擺脫 順序性程式設計思維 的重要一步。
回顧LiveData:從處境尷尬到鹹魚翻身
我們都知道Google在去年的 I/O 大會非常隆重地推出了一系列的 架構元件,本文的主角,LiveData 正是其中之一,和Lifecycle
、ViewModel
、Room
比較起來,LiveData
可以說是最受關注的元件也不為過,遺憾的是,在釋出的最初,關注點是因為它飽含爭議,相當一部分的開發者認為——LiveData
實在太 雞肋 了!
2017年的 Android
技術領域,RxJava
無疑是炙手可熱的名詞之一,其 觀察者模式 和 鏈式呼叫 所表現出來的 API 優秀地設計,使得它位於很多 Android專案技術選型中的 第一序列。
這時 Google 隆重推出了具有類似功能的 LiveData
(其本質就是觀察者模式),可以說是有點初生牛犢不怕虎的感覺,開發者們不由自主將LiveData
和 RxJava
進行了對比,結論基本出奇的一致—— LiveData
所提供的功能,RxJava
完全足以勝任,而後者卻同時具有龐大的生態圈,這是LiveData
短時間內難以撼動(替代)的。
時至今日,LiveData
的使用者越來越多,最主要的原因當然和Google的強力支援不無關係,但是LiveData
本身優秀的設計和輕量級也吸引了越來越多開發者的青睞。
現在我們需要去了解它了,我們都知道,LiveData
本質是 觀察者模式 的體現,可關鍵的問題是:
觀察者模式到底是啥?!
討論這個問題之前,我們先看看 LiveData
的用法,這實在沒什麼技術難度,比如,你可以這樣例項化一個LiveData
並使用它:
如你所見,LiveData
實際上就像一個 容器, 本文中它儲存了一個String
型別的引用,每當這個容器內 String
的資料發生變化,我們都能在回撥函式中進行對應的處理,比如 Toast。
這似乎和我們日常用到的 Button
控制元件的 setOnClickListener()
非常相似,實際上點選事件的監聽也正是 觀察者模式 的一種體現,對於觀察者來說,它並不關心觀察物件 資料是如何過來的,而只關心資料過來後 進行怎樣的處理。
這也就是說,事件發射的上游 和 接收事件的下游 互不干涉,大幅降低了互相持有的依賴關係所帶來的強耦合性。
我依然堅持學習原理比學習如何應用的優先順序更高,因此我們先來一一探究LiveData
本身設計中存在的那些閃光點背後的故事。
LiveData是如何避免記憶體洩漏的
我們都知道,RxJava
在使用過程中,避免記憶體洩漏是一個不可忽視的問題,因此我們一般需要藉助三方庫比如RxLifecycle
、AutoDispose
來解決這個問題。
而反觀LiveData
,當它被我們的Activity
訂閱觀察,這之後Activity
如果finish()
掉,LiveData
本身會自動“清理”以避免記憶體洩漏。
這是一個非常好用的特性,它的實現原理非常簡單,其本質就是利用了Jetpack 架構元件中的另外一個成員—— Lifecycle。
讓我們來看看LiveData
被訂閱時內部的程式碼:
原始碼中的邏輯非常複雜,我們只關注核心程式碼:
- 1.首先我們在呼叫
LiveData.observer()
方法時,傳遞的第一個引數Acitivity
實際被向上抽象成為了LifecycleOwner
,第二個引數Obserser
實際就是我們的觀察後的回撥。
這裡我們需要注意的是,執行
LiveData.observer()
方法時 必須處於主執行緒,否則會因為斷言失敗而丟擲異常。
- 2.方法內部實際上將我們傳入的2個引數包裝成了一個新的
LifecycleBoundObserver
物件,它實現了 Lifecycle 元件中的LifecycleObserver
介面:
這裡就解釋了為什麼LiveData
能夠 自動解除訂閱而避免記憶體洩漏 了,因為它內部能夠感應到Activity
或者Fragment
的生命週期。
這種設計非常巧妙——在我們初識 Lifecycle 元件時,總是下意識認為它能夠對大的物件進行有效生命週期的管理(比如 Presenter),實際上,這種生命週期的管理我們完全可以應用到各個功能的基礎元件中,比如大到吃記憶體的 MediaPlayer(多媒體播放器)、繪製設計複雜的 自定義View,小到隨處可見的LiveData
,都可以通過實現LifecycleObserver
介面達到 感應生命週期並內部釋放重的資源 的目的。
關於上述程式碼中註釋了 更新LiveData的活躍狀態 的原始碼,我們先跳過,稍後我們會詳細探討它。
-
- 我們繼續回到上上一個原始碼片段的第三步中,對於一個可觀察的
LiveData
來講,當然存在多個觀察者同時訂閱觀察的情況,因此考慮到這一點,Google的工程師們為每一個LiveData
配置了一個Map
儲存所有的觀察者。
- 我們繼續回到上上一個原始碼片段的第三步中,對於一個可觀察的
-
4.到了這一步,我們將第2步包裝生成的物件交給我們傳入的
Activity
,讓它在不同的生命週期事件中去逐一通知其所有的觀察者,當然也包含了我們的LiveData
。
資料更新後如何通知到回撥方法?
LiveData
原生的API提供了2種方式供開發者更新資料, 分別是 setValue()
和postValue()
,官方文件明確標明:setValue()
方法必須在 主執行緒 進行呼叫,而postValue()
方法更適合在執行較重工作 子執行緒 中進行呼叫(比如網路請求等)——在所有情況下,呼叫setValue()
或postValue()
都會 觸發觀察者並更新UI。
柿子挑軟的捏,我們先看setValue()
方法的實現原理:
通過保留最終的核心程式碼,我們很清晰瞭解了setValue()
方法為什麼能更新LiveData
的值,並且通知到回撥函式中的程式碼去執行,比如更新UI。
但是我們知道,普遍情況下,Android不允許在子執行緒更新UI,但是postValue()
方法卻可以在子執行緒更新LiveData()
的資料,並通知更新UI,這是如何實現的呢?
其實答案已經呼之欲出了,就是通過 Handler
:
現在你已經對LiveData
整體了一個基本的瞭解了,接下來讓我們開始去探究更細節的閃光點。
看完原始碼,你告訴我才算入門?
LiveData
本身非常簡單,畢竟它本身的原始碼一共也就500行左右,也許你要說 準備面試粗讀一遍原始碼就夠了,很遺憾,即使是粗讀了原始碼,也很難說能夠完全招架更深入的提問...
讓我們來看一道題目:在下述Activity完整的生命週期中,Activity
一共觀察到了幾次資料的變更——即 一共列印了幾條Log ?(補充糾正,onStop()方法中值應該為 "onStop")
公佈答案:
意外的是,livedata.observer()
的本次觀察並沒有觀察到 onCreate、onStop 和 onDestroy 的資料變更。
為什麼會這樣?
還記得上文提到過2次的 LiveData的活躍狀態(Active) 相關程式碼嗎?實際上,LiveData
內部儲存的每一個LifecycleBoundObserver
本身都有shouldBeActive
的狀態:
現在我們明白了,原來並不是只要在onDestroy()
之前為LiveData
進行更新操作,LiveData
的觀察者就能響應到對應的事件的。
雖然我們明白了這一點,但是如果更深入的思考,你會又多一個問題,那就是:
- 既然
LiveData
已經能夠實現在onDestroy()
的生命週期時自動解除訂閱,為什麼還要多此一舉設定一個Active
的狀態呢?
仔細想想,其實也不難得到答案,Activity
並非只有onDestroy()
一種狀態的,更多時候,新的Activity
執行在棧頂,舊的Activity
就會執行在 background
——這時舊的Activity
會執行對應的onPause()
和onStop()
方法,我們當然不會關心執行在後臺的Activity
所觀察的LiveData
物件(即使資料更新了,我們也無從進行對應UI的更新操作),因此LiveData
進入 **InActive(待定、非活躍)**狀態,return
並且不去執行對應的回撥方法,是 非常縝密的優秀設計 。
當然,有同學提出,我如果希望這種情況下,Activity
在後臺依然能夠響應資料的變更,可不可以呢?當然可以,LiveData
此外還提供了observerForever()
方法,在這種情況下,它能夠響應到任何生命週期中資料的變更事件:
除此之外,原始碼中處處都是優秀的細節,比如對於observe()
方法和observerForever()
方法對應生成的包裝類,後者方法生成的是AlwaysActiveObserver
物件,統一抽象為ObserverWrapper
。
這種即使只有2種不同場景,也通過程式碼的設計,將公共業務進行向上抽離為抽象類的嚴謹,也非常值得我們學習。
小結,與更深入的思考
本來寫了更多,篇幅所限,最終還是決定刪除了相當一部分和 RxJava
有關的內容,這些內容並非是將 LiveData
和 RxJava
進行對比一決高下—— 例如,Google官方提供了 LiveData
和 RxJava
互相進行轉換的工具類:
developer.android.com/reference/a…
值得玩味的是,官方的工具類中,LiveData
向RxJava
的轉換方法,返回值並非是一個Flowable
,而是一個Publisher
介面:
正如我在註釋中標註的,這個工具方法返回的是一個介面,很大程度上限制了我們對RxJava
眾多強大操作符的使用,這是否是來自Google的惡意?
當然不是,對於這種行為,我的理解是Google對於LiveData
本身嚴格的約束——它只應該用於進行資料的觀察,而不是花哨的操作;轉換為Flowable當然非常簡單,但是這種行為是否屬於LiveData
本身職責的逾越,更準確來說,是否屬於不必要的過度設計?這些是我們需要去細細揣度的。
我無從驗證我的理解是否正確,但是我的這個理由已經足夠說服我自己,再往下已不再是LiveData
的範疇,關於這一點我將會專門起一篇文章去進行更深入的探討,歡迎關注。
--------------------------廣告分割線------------------------------
系列文章
爭取打造 Android Jetpack 講解的最好的部落格系列:
Android Jetpack 實戰篇:
關於我
Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的個人部落格或者Github。
如果您覺得文章還差了那麼點東西,也請通過關注督促我寫出更好的文章——萬一哪天我進步了呢?