今天推送一篇關於 Android 架構的最佳實踐項目。網絡
Jetpack Lifecycle架構
Lifecycle 爲何能解決上述這些問題?框架
Jetpack LiveDataide
LiveData 有個坑須要注意佈局
Jetpack ViewModelpost
ViewModel 爲何能作到這幾點?編碼
Jetpack DataBindingurl
DataBinding 就是來解決這些問題.net
Lifecycle 的存在,主要是爲了解決 生命週期管理 的一致性問題設計
在 Lifecycle 面市前,生命週期管理 純靠手工維持,這樣就容易滋生大量的一致性問題。
例如跨頁面共享的 GpsManager 組件,在每一個依賴它的 Activity 的 onResume 和 onPause 中都須要 手工 激活、解綁 和 叫停。
那麼 隨着 Activity 的增多,這種手工操做 埋下的一致性隱患 就會指數級增加:
一方面,凡是手工維持的,開發者容易疏忽,特別是工做交接給其餘同事時,同事並不能及時注意到這些細節。
另外一方面,分散的代碼不利於修改,往後除了激活、叫停,如有其餘操做須要補充(例如狀態監聽),那麼每一個 Activity 都須要額外書寫一遍。
Lifecycle 經過 模板方法模式 和 觀察者模式,將生命週期管理的複雜操做,所有在做爲 LifecycleOwner 的基類中(例如視圖控制器的基類)封裝好,默默地在背後爲開發者指揮若定,
開發者於是得以在視圖控制器(子類)中只需一句:
getLifecycle().addObserver(GpsManager.getInstance) ,優雅地完成 第三方組件在本身內部 對 LifecycleOwner 生命週期的感知。
除了解決一致性問題,這樣作還 順帶地提供了其餘 2 個好處:
1. 規避 爲監聽狀態 而 注入視圖控制器 的作法
當須要監聽狀態時,以往咱們的作法是 經過方法手工注入 Activity 等參數,這埋下了內存泄漏的隱患 —— 由於團隊中的新手容易因這是個 Activity,而在往後誤將其依賴給組件中的其餘成員。
現現在,咱們能夠直接在組件內部 點到爲止 地監聽 LifecycleOwner 的狀態,從而規避這種不恰當的使用。
2. 規避 爲追溯事故來源 而 注入視圖控制器 的作法
當發生事故時,以往咱們若想在組件中 追溯事故來源,一樣不得不從方法中直接注入 Activity 等,這一樣埋下了內存泄漏的隱患。現現在組件因實現了 DefaultLifecycleObserver,而得以經過生命週期回調方法中的 LifecycleOwner 參數,在方法做用域中 便可得知事故來源,無需更多帶有隱患的操做。
若是這麼說還不理解的話,可具體參考我在 《爲你還原一個真實的 Jetpack Lifecycle》 中提供的 GpsManager 案例,本文再也不累述。
LiveData 的存在,主要是爲了幫助 新手老手 都能不假思索地遵循 經過惟一可信源分發狀態 的標準化開發理念,從而使在快速開發過程當中 難以追溯、難以排查、不可預期 的問題所發生的機率下降到最小。
在 LiveData 面市前,咱們分發狀態,可能是經過 EventBus 或 Java Interface 來完成的。無論你是用於網絡請求回調的狀況,仍是跨頁面通訊的狀況。
那這形成了什麼問題呢?首先,EventBus 只是純粹的 Bus,它 缺少上述提到的 標準化開發理念 的約束,那麼人們在使用這個框架時,容易因 去中心化 地濫用,而形成 諸如 毫無防備地收到 預期外的 不明來源的推送、拿到過期的數據 及 事件源追溯複雜度 爲 n² 的局面。
而且,EventBus 自己缺少 Lifecycle 的加持,存在生命週期管理的一致性問題。這是 EventBus 的硬傷,也是我拒絕使用 EventBus 的最主要因素。
對上述情況不理解的,可具體參考我在 《LiveData 不爲人知的 身世背景 和 獨特使命》 中提供的 播放器狀態全局通知 的案例
首先,LiveData 是在 Google 但願確立 標準化、規範化 的開發模式 —— 這樣一種背景下誕生的,於是爲了達成這個艱鉅的 使命,Google 十分克制地將其設計爲,僅支持狀態的輸入和監聽,從而,它不得不 在單例的配合下,承上啓下地完成 狀態 從 惟一可信源 到 視圖控制器 的輸送。
(ViewModel 姑且也算是一種單例,一種工廠模式實現的僞單例。惟一可信源是指 生命週期獨立於 視圖控制器的 數據組件,一般是 單例 或共享 ViewModel)
這使得任何一次狀態推送,均可預期、都能方便地追溯來源,而不至於在 事件追溯複雜度爲 n² 的迷宮中白費時間。(即,不管是從哪一個視圖控制器發起的 對某個共享狀態改變的請求,狀態最終的改變 都由 做爲惟一可信源的 單例或 SharedViewModel 來一對多地通知改變)
而且,這種承上啓下的方式,使得單向依賴成爲可能:單例無需經過 Java Interface 回調通知視圖控制器,從而規避了視圖控制器 被生命週期更長的單例 依賴 所埋下的內存泄漏的隱患。
不過,LiveData 的設計有個坑,這裏我順帶提一下。
爲了在視圖控制器發生重建後,可以 自動灌倒 所觀察的 LiveData 的最後一次數據,LiveData 被設計爲粘性事件。
—— 我姑且認爲這是個拓展性不佳的設計,甚至能夠說是一個 bug,
由於 MVVM 是一個總體,既然 ViewModel 支持共享做用域,而且官方文檔都認可了經過 共享 ViewModel 來實現跨頁面通訊的需求,
那麼基於 「開閉原則」,LiveData 理應提供一個與 MutableLiveData 平級的底層支持,專門用於非粘性的事件通訊的狀況,不然直接在跨頁面通訊中使用 MutableLiveData 必形成 事件回調的一致性問題 及 難以預期的錯誤。
關於非粘性 LiveData 的實現,網上存在經過 「事件包裝類」(只適合 kotlin 的狀況) 和 「反射干預 LastVersion」 (適用於 Java 的狀況)兩種方式來解決:
juejin.im/post/5b2b1b…
blog.csdn.net/geyuecang/a…
不管是使用哪種實現,我都建議 遵循傳統 LiveData 所遵循的開發理念,經過惟一可信源分發狀態,來方便事件源頭的追溯。對於 「去中心化」 的 Bus 方式,我拒絕在項目中這樣使用。
(具體我會在將來開源的最佳實踐項目中 展現 UnPeekLiveData 的使用)
ViewModel 的存在,主要是爲了解決 狀態管理 和 頁面通訊 的問題。
ViewModel 的本職工做是 狀態託管 和 狀態管理的分治,也即當視圖控制器重建時,
對於輕量的狀態,能夠經過視圖控制器基類的 saveInstanceState 機制,以序列化的方式完成存儲和恢復。
對於重量級的狀態,例如經過網絡請求獲得的 List,能夠經過生命週期長於視圖控制器的 ViewModel 持有,從而得以直接從 ViewModel 恢復,而不是以效率較低的序列化方式。
在 Jetpack ViewModel 面市以前,MVP 的 Presenter 和 MVVM - Clean 的 ViewModel 都不具有狀態管理分治的能力。
Presenter 和 Clean ViewModel 的生命週期都與視圖控制器同生共死,於是它們頂可能是爲 DataBinding 提供狀態的託管,而沒法實現狀態的分治。
到了 Jetpack 這一版,ViewModel 以精妙的設計,達成了狀態管理,以及可共享的做用域。
其實這版主要是基於 工廠模式,使得 ViewModel 被 LifecycleOwner 所持有、經過 ViewModelProvider 來引用,
因此 它既相似於單例:—— 當被做爲 LifecycleOwner 的 Activity 持有時,可以脫離 Activity 旗下 Fragment 的生命週期,從而實現做用域共享,
實際上又不是單例:—— 生命週期跟隨 做爲 LifecycleOwner 的視圖控制器,當 Owner(Activity 或 Fragment)被銷燬時,它也被 clear。
此外,出於對視圖控制器重建的考慮,Google 在視圖控制器基類中經過 retain 機制對 ViewModel 進行了保留。
所以,對於 做用域共享 和 視圖重建 的狀況,狀態因無缺地被保留,而得以被視圖控制器在恢復時直接使用。
再者,因爲存在 共享做用域的考慮,因此 ViewModel 自己也承擔了跨頁面通訊(例如事件回調)的職責。前面在介紹 LiveData 時,對於 LiveData 在事件通訊時粘性設計的問題已經介紹過了,這裏再也不累述。
DataBinding 的存在,主要是爲了解決 視圖調用 的一致性問題。
在 DataBinding 面市前,咱們若要改變視圖的狀態,首先就要引用該視圖,例如 textView.setText(),
這形成什麼問題呢?
當頁面存在橫、豎佈局,且兩種佈局的控件存在差別,例如橫屏存在 textView 控件,而豎屏沒有,那麼咱們就不得不在視圖控制器中爲 textView 作判空處理,這就形成了一致性問題 —— 容易疏忽而忘記判空,畢竟頁面多達數十個、每一個頁面的控件也無數。
那怎麼辦呢?
經過在佈局中與可觀察的數據發生綁定,那麼當該數據被 set 新的內容時,控件也將獲得通知和刷新。
換言之,在使用 DataBinding 後,惟一的改變是,你無需手工調用視圖來 set 新狀態,你只需 set 數據自己。
於是,DataBinding 並不是許多人不假思索認爲的,將 UI 邏輯搬到 XML 中寫 從而難以調試 —— 事實根本不是這樣的:
DataBinding 只負責綁定數據、負責做爲 UI 邏輯末端的狀態的改變(也即它是一個不可再分的原子操做,原本就不須要調試),本來在視圖控制器中 UI 邏輯怎麼寫,如今仍是怎麼寫,只不過再也不須要 textView.setText(xxx),而是直接 xxx.set()。
因此在 DataBinding 的幫助下,好處總共有多少個呢?
1.規避了視圖狀態的 一致性問題 —— 無需手工判空。
2.規避了視圖狀態的 一致性問題,乃至無需視圖調用,從而徹底不用編寫 findViewById。
3.就算要調用視圖,也不用 findViewById,而是直接經過 binding 來引用。
4.先前的 UI 邏輯基本不用改動,改的只是做爲末端的狀態改變的方式。
此外,DataBinding 有個大殺器就是,能爲控件提供自定義屬性的 BindingAdapter,它不只能夠解決 圓角 Drawable 複用的問題(你懂得),還能夠實現 imageView 直接綁定 url 等需求,總之,沒有它辦不到的,只有你想不到的,DataBinding 的好處等着你挖掘。
關於 DataBinding 的注意事項,以及屢試不爽的排坑技巧,可具體參考 《從 被誤解 到 真香 的 Jetpack DataBinding!》,這裏不作累述。
Lifecycle 的存在,主要是爲了解決 生命週期管理 的一致性問題。
LiveData 的存在,主要是爲了幫助 新手老手 都能不假思索地 遵循 經過惟一可信源分發狀態 的標準化開發理念,從而在快速開發過程當中 規避一系列 難以追溯、難以排查、不可預期 的問題。
ViewModel 的存在,主要是爲了解決 狀態管理 和 頁面通訊 的問題。
DataBinding 的存在,主要是爲了解決 視圖調用 的一致性問題。
它們的存在 大都是爲了 在軟件工程的背景下 解決一致性的問題、將容易出錯的操做在後臺封裝好,方便使用者快速、穩定、不產生預期外錯誤地編碼。
這樣說,你理解了嗎?
文章不易,若是你們喜歡這篇文章,或者對你有幫助但願你們多多, 點贊,轉發,關注。文章會持續更新的。絕對乾貨!!!