Android官方架構元件LiveData: 觀察者模式領域二三事

卻把清梅嗅發表於2018-12-28

本文是 《Android Jetpack 官方架構元件》 系列的最後一篇文章,和一些朋友的觀點不同的是,我認為它是 最重要 的核心元件,因為 LiveData本身很簡單,但其代表卻正是 MVVM 模式最重要的思想,即 資料驅動檢視(也有叫觀察者模式、響應式等)——這也是擺脫 順序性程式設計思維 的重要一步。

本文預設讀者已經學習了 Lifecycle , 歡迎關注筆者的Jetpack系列:

爭取打造 Android Jetpack 講解的最好的部落格系列

Android Jetpack 實戰篇

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

我們都知道Google在去年的 I/O 大會非常隆重地推出了一系列的 架構元件,本文的主角,LiveData 正是其中之一,和LifecycleViewModelRoom比較起來,LiveData可以說是最受關注的元件也不為過,遺憾的是,在釋出的最初,關注點是因為它飽含爭議,相當一部分的開發者認為——LiveData 實在太 雞肋 了!

2017年的 Android 技術領域,RxJava無疑是炙手可熱的名詞之一,其 觀察者模式鏈式呼叫 所表現出來的 API 優秀地設計,使得它位於很多 Android專案技術選型中的 第一序列

這時 Google 隆重推出了具有類似功能的 LiveData (其本質就是觀察者模式),可以說是有點初生牛犢不怕虎的感覺,開發者們不由自主將LiveDataRxJava 進行了對比,結論基本出奇的一致—— LiveData所提供的功能,RxJava完全足以勝任,而後者卻同時具有龐大的生態圈,這是LiveData短時間內難以撼動(替代)的。

時至今日,LiveData的使用者越來越多,最主要的原因當然和Google的強力支援不無關係,但是LiveData本身優秀的設計和輕量級也吸引了越來越多開發者的青睞。

現在我們需要去了解它了,我們都知道,LiveData 本質是 觀察者模式 的體現,可關鍵的問題是:

觀察者模式到底是啥?!

討論這個問題之前,我們先看看 LiveData 的用法,這實在沒什麼技術難度,比如,你可以這樣例項化一個LiveData並使用它:

為了保證程式碼簡潔可讀,示例程式碼我使用了Kotlin

如你所見,LiveData實際上就像一個 容器, 本文中它儲存了一個String型別的引用,每當這個容器內 String的資料發生變化,我們都能在回撥函式中進行對應的處理,比如 Toast

這似乎和我們日常用到的 Button 控制元件的 setOnClickListener() 非常相似,實際上點選事件的監聽也正是 觀察者模式 的一種體現,對於觀察者來說,它並不關心觀察物件 資料是如何過來的,而只關心資料過來後 進行怎樣的處理

這也就是說,事件發射的上游接收事件的下游 互不干涉,大幅降低了互相持有的依賴關係所帶來的強耦合性。

我依然堅持學習原理比學習如何應用的優先順序更高,因此我們先來一一探究LiveData本身設計中存在的那些閃光點背後的故事。

LiveData是如何避免記憶體洩漏的

感謝評論區的提醒,不同版本之間的原始碼有所差異,本文原始碼基於 androidx.lifecycle:lifecycle-livedata:2.0.0 版本,特此說明。

我們都知道,RxJava在使用過程中,避免記憶體洩漏是一個不可忽視的問題,因此我們一般需要藉助三方庫比如RxLifecycleAutoDispose來解決這個問題。

而反觀LiveData,當它被我們的Activity訂閱觀察,這之後Activity如果finish()掉,LiveData本身會自動“清理”以避免記憶體洩漏。

這是一個非常好用的特性,它的實現原理非常簡單,其本質就是利用了Jetpack 架構元件中的另外一個成員—— Lifecycle

讓我們來看看LiveData被訂閱時內部的程式碼:

Android官方架構元件LiveData: 觀察者模式領域二三事

原始碼中的邏輯非常複雜,我們只關注核心程式碼:

  • 1.首先我們在呼叫LiveData.observer()方法時,傳遞的第一個引數Acitivity實際被向上抽象成為了 LifecycleOwner,第二個引數Obserser實際就是我們的觀察後的回撥。

這裡我們需要注意的是,執行LiveData.observer()方法時 必須處於主執行緒,否則會因為斷言失敗而丟擲異常。

  • 2.方法內部實際上將我們傳入的2個引數包裝成了一個新的 LifecycleBoundObserver物件,它實現了 Lifecycle 元件中的LifecycleObserver介面:

Android官方架構元件LiveData: 觀察者模式領域二三事

這裡就解釋了為什麼LiveData能夠 自動解除訂閱而避免記憶體洩漏 了,因為它內部能夠感應到Activity或者Fragment的生命週期。

這種設計非常巧妙——在我們初識 Lifecycle 元件時,總是下意識認為它能夠對大的物件進行有效生命週期的管理(比如 Presenter),實際上,這種生命週期的管理我們完全可以應用到各個功能的基礎元件中,比如大到吃記憶體的 MediaPlayer(多媒體播放器)、繪製設計複雜的 自定義View,小到隨處可見的LiveData,都可以通過實現LifecycleObserver介面達到 感應生命週期並內部釋放重的資源 的目的。

關於上述程式碼中註釋了 更新LiveData的活躍狀態 的原始碼,我們先跳過,稍後我們會詳細探討它。

    1. 我們繼續回到上上一個原始碼片段的第三步中,對於一個可觀察的LiveData來講,當然存在多個觀察者同時訂閱觀察的情況,因此考慮到這一點,Google的工程師們為每一個LiveData配置了一個Map儲存所有的觀察者。
  • 4.到了這一步,我們將第2步包裝生成的物件交給我們傳入的 Activity,讓它在不同的生命週期事件中去逐一通知其所有的觀察者,當然也包含了我們的LiveData

資料更新後如何通知到回撥方法?

LiveData原生的API提供了2種方式供開發者更新資料, 分別是 setValue()postValue(),官方文件明確標明:setValue()方法必須在 主執行緒 進行呼叫,而postValue()方法更適合在執行較重工作 子執行緒 中進行呼叫(比如網路請求等)——在所有情況下,呼叫setValue()postValue()都會 觸發觀察者並更新UI

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

Android官方架構元件LiveData: 觀察者模式領域二三事

通過保留最終的核心程式碼,我們很清晰瞭解了setValue()方法為什麼能更新LiveData的值,並且通知到回撥函式中的程式碼去執行,比如更新UI。

但是我們知道,普遍情況下,Android不允許在子執行緒更新UI,但是postValue()方法卻可以在子執行緒更新LiveData()的資料,並通知更新UI,這是如何實現的呢?

其實答案已經呼之欲出了,就是通過 Handler

Android官方架構元件LiveData: 觀察者模式領域二三事

現在你已經對LiveData整體了一個基本的瞭解了,接下來讓我們開始去探究更細節的閃光點。

看完原始碼,你告訴我才算入門?

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

讓我們來看一道題目:在下述Activity完整的生命週期中,Activity一共觀察到了幾次資料的變更——即 一共列印了幾條Log ?(補充糾正,onStop()方法中值應該為 "onStop")

Android官方架構元件LiveData: 觀察者模式領域二三事

公佈答案:

Android官方架構元件LiveData: 觀察者模式領域二三事

意外的是,livedata.observer()的本次觀察並沒有觀察到 onCreateonStoponDestroy 的資料變更。

為什麼會這樣?

還記得上文提到過2次的 LiveData的活躍狀態(Active) 相關程式碼嗎?實際上,LiveData內部儲存的每一個LifecycleBoundObserver本身都有shouldBeActive的狀態:

Android官方架構元件LiveData: 觀察者模式領域二三事

現在我們明白了,原來並不是只要在onDestroy()之前為LiveData進行更新操作,LiveData的觀察者就能響應到對應的事件的。

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

  • 既然LiveData已經能夠實現在onDestroy()的生命週期時自動解除訂閱,為什麼還要多此一舉設定一個Active的狀態呢?

仔細想想,其實也不難得到答案,Activity並非只有onDestroy()一種狀態的,更多時候,新的Activity執行在棧頂,舊的Activity就會執行在 background——這時舊的Activity會執行對應的onPause()onStop()方法,我們當然不會關心執行在後臺的Activity所觀察的LiveData物件(即使資料更新了,我們也無從進行對應UI的更新操作),因此LiveData進入 **InActive(待定、非活躍)**狀態,return並且不去執行對應的回撥方法,是 非常縝密的優秀設計

當然,有同學提出,我如果希望這種情況下,Activity在後臺依然能夠響應資料的變更,可不可以呢?當然可以,LiveData此外還提供了observerForever()方法,在這種情況下,它能夠響應到任何生命週期中資料的變更事件:

Android官方架構元件LiveData: 觀察者模式領域二三事

除此之外,原始碼中處處都是優秀的細節,比如對於observe()方法和observerForever()方法對應生成的包裝類,後者方法生成的是AlwaysActiveObserver物件,統一抽象為ObserverWrapper

Android官方架構元件LiveData: 觀察者模式領域二三事

這種即使只有2種不同場景,也通過程式碼的設計,將公共業務進行向上抽離為抽象類的嚴謹,也非常值得我們學習。

小結,與更深入的思考

本來寫了更多,篇幅所限,最終還是決定刪除了相當一部分和 RxJava 有關的內容,這些內容並非是將 LiveDataRxJava 進行對比一決高下—— 例如,Google官方提供了 LiveDataRxJava 互相進行轉換的工具類:

developer.android.com/reference/a…

值得玩味的是,官方的工具類中,LiveDataRxJava的轉換方法,返回值並非是一個Flowable,而是一個Publisher介面:

Android官方架構元件LiveData: 觀察者模式領域二三事

正如我在註釋中標註的,這個工具方法返回的是一個介面,很大程度上限制了我們對RxJava眾多強大操作符的使用,這是否是來自Google的惡意

當然不是,對於這種行為,我的理解是Google對於LiveData本身嚴格的約束——它只應該用於進行資料的觀察,而不是花哨的操作;轉換為Flowable當然非常簡單,但是這種行為是否屬於LiveData本身職責的逾越,更準確來說,是否屬於不必要的過度設計?這些是我們需要去細細揣度的。

我無從驗證我的理解是否正確,但是我的這個理由已經足夠說服我自己,再往下已不再是LiveData的範疇,關於這一點我將會專門起一篇文章去進行更深入的探討,歡迎關注。

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

關於我

Hello,我是卻把清梅嗅,如果您覺得文章對您有價值,歡迎 ❤️,也歡迎關注我的部落格或者Github

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

相關文章