
大家好,我是《Jetpack MVVM 精講》和《Jetpack MVVM 最佳實踐》的作者 KunMinX,
在過去一年里,我們分別在各渠道的維護和交流中,收集到許多新上手的小伙伴在把 Jetpack MVVM 應用到自己項目中時,最頻繁提及的問題,
隨著 Jetpack MVVM 的普及,高頻問題也越來越多地出現(xiàn)在 面試或重構(gòu)工作中,
考慮到這些四處分散的 Q&A(提問和解答) 不便于新上手的小伙伴查閱,因而單獨準備了本文,點開就能直接查看到 從數(shù)百位讀者的數(shù)千次提問中 精心篩選出的高頻 Q&A,
所以這樣的文章,從前乃至往后就只提供這么一篇,請珍惜地享用。
?
目錄一覽
- 訂閱讀者交流群 高頻 Q&A TOP 5
- TOP 1:Jetpack MVVM 下的頁面通信怎么做?
- TOP 2:LiveData “數(shù)據(jù)倒灌” 是什么情況,如何解決?
- TOP 3:邏輯為什么不在 ViewModel 中寫?
- TOP 4:為什么不用 LiveDataBus?
- TOP 5:Navigation replace 方式返回時,怎么恢復視圖狀態(tài)?
- 《最佳實踐》項目 issue 區(qū) 高頻 Q&A TOP 5
- TOP 1:頁面 onPause 的時候,不是不該收到消息嗎?
- TOP 2:《最佳實踐》項目中的 “DataBinding 嚴格模式” 是怎么回事?
- TOP 3:綁定視圖狀態(tài),LiveData 和 ObservableField,怎么取舍?
- TOP 4:LiveData observe 回調(diào)走了多次,該如何處理?
- TOP 5:將《最佳實踐》的 Navigation 修改版引入到自己項目,結(jié)果還是走的 replace,怎么辦?
?
訂閱讀者交流群 高頻 Q&A TOP 5
TOP 1:Jetpack MVVM 下的頁面通信怎么做?
解答:通過 SharedViewModel 來完成。
追問:為什么?
解答:我們之所以選擇 Application 級的 ViewModel,而不是靜態(tài)變量或傳統(tǒng) bus 來完成 應用內(nèi)頁面間的消息通信(事件回調(diào)等),是考慮到:
1.該 ViewModel 被封裝在視圖控制器(Activity/Fragment)的基類,使得消息能夠 僅限于在視圖控制器之間傳播,而不污染到之外的區(qū)域。
2.同時也可避免被外部的組件拿到,而造成不可預期的推送。
具體可見《最佳實踐》項目中對 SharedViewModel 的使用。
?
TOP 2:LiveData “數(shù)據(jù)倒灌” 是什么情況,如何解決?
解答:“數(shù)據(jù)倒灌” 現(xiàn)象是我全網(wǎng)首創(chuàng)的對某類現(xiàn)象的概括,所以網(wǎng)上大概搜不到這類描述。
數(shù)據(jù)倒灌是 專指 在 頁面通信(事件回調(diào))的場景下,通過 SharedViewModel 的 LiveData 給當前頁通知過一次,并返回上一頁,下次再進入當前頁時重復收到推送的情況。
目前《最佳實踐》項目中通過 EventLiveData 解決了這類問題,具體可查看最新源碼。
| Event 包裝器 | 重寫底層 | EventLiveData |
|---|---|---|
image
|
image
|
image
|
?
TOP 3:邏輯為什么不在 ViewModel 中寫?
解答:Jetpack MVVM 主要遵循 數(shù)據(jù)驅(qū)動 和 關(guān)注點分離 這兩大特性,
其中關(guān)注點分離 是通過 “最小知道原則” 來體現(xiàn):
UI 邏輯在視圖控制器(Activity / Fragment)中寫,
業(yè)務(wù)邏輯在數(shù)據(jù)層(例如 DataRepository)寫。
ViewModel 作為 視圖控制器 和 數(shù)據(jù)層 溝通的橋梁,其自身應保持輕量,以勝任 “承上啟下” 的角色(保持整體框架的 單向依賴)。
而且,就像認識其他問題一樣,“邏輯該在 Activity 中寫還是 ViewModel 中寫”,
要搞清楚這個問題,我們 仍然需要首先搞清楚,這件事的背景是什么 ——
是在多人協(xié)作的軟件工程的背景下。
?????? 劃重點
這意味著什么呢?意味著,一旦 你將 UI 邏輯放在 ViewModel 中寫了,后續(xù)就不可控了,
你的同事如果不熟悉這一套開發(fā)模式,在 “破窗效應” 的驅(qū)使下,就可能直接在 ViewModel 中取 context、取各種不該取的東西,最終內(nèi)存泄漏什么的,全都來了。
綜上,ViewModel 的職責邊界就是幫助 Activity/Fragment 托管數(shù)據(jù),不適合在 ViewModel 中寫邏輯。
更多細節(jié)內(nèi)容詳見 《有了 Jetpack ViewModel . . . 真的可以為所欲為!》 中的介紹。
?
TOP 4:為什么不用 LiveDataBus?
解答:原因同上。
不使用 LiveDataBus 是因為,我們是以 在 多人協(xié)作、頁面繁雜的 軟件工程 為背景來談?wù)摷軜?gòu)設(shè)計的。在這樣的背景下,任何微不足道的隱患,都可能被無限放大。
bus 自身 缺乏唯一可信源的理念約束 以及 難以追溯事件源對象,應徹底從項目中移除,以免團隊新手的誤用乃至濫用。
具體緣由可參考 《LiveData 鮮為人知的 身世背景 和 獨特使命》 中的介紹。
與此同時,盡可能使用 單例或全局 ViewModel 來托管 liveData,這樣調(diào)試時能根據(jù)內(nèi)存中的 liveData 對象找到事件源。LiveDataBus 這種通過 tag 來標記的,難以找到。
?
TOP 5:Navigation replace 方式返回時,怎么恢復視圖狀態(tài)?
解答:Navigation 的 FragmentNavigator,官方寫法是通過 replace 來啟動新 Fragment,這可能造成返回時重繪頁面等問題,對此有兩種辦法,一種是重寫 FragmentNavigator,使之通過 show hide 來啟動新 Fragment,另一種是在 onCreateView 中復用上一次實例化好的 View。
具體操作和注意事項可參考 《就算不用 Jetpack Navigation,也請務(wù)必領(lǐng)略的聲明式編程之美!》 文末的詳細補充,以及我和 Flywith24 在 《我的碎片很聽話,你的 Fragment 有自己的想法》 評論區(qū) 22 樓關(guān)于 replace 方式返回時視圖狀態(tài)恢復的討論。
?
《最佳實踐》項目 issue 高頻 Q&A TOP 5:
TOP 1:頁面 onPause 的時候,不是不該收到消息嗎?
解答:看到網(wǎng)上有不少 以訛傳訛的網(wǎng)文 傳播 “頁面 onPause 時不會收到 LiveData 通知” 等不實觀點,給讀者們徒添困擾、耽誤大量時間,特此辟謠:
事實恰恰相反,onPause 可以收到,而 onStart 不是所有場景都能收到(截至 2020.2,Activity 能,F(xiàn)ragment 不能) ——
只有 onResume 和 onPause 是介于 STARTED、RESUMED 狀態(tài)之間,也即只有這兩個生命周期節(jié)點 100% 確定能夠收到 LiveData 的推送。
具體緣由詳見專欄 《為你還原一個真實的 Jetpack Lifecycle》 文末 最新補充
?
TOP 2:《最佳實踐》項目中的 “DataBinding 嚴格模式”是怎么回事?
解答:“嚴格模式” 是我基于對 “數(shù)據(jù)驅(qū)動” 的本質(zhì)的理解,而全網(wǎng)首創(chuàng)的 軟件工程安全的 “純粹數(shù)據(jù)驅(qū)動” 的寫法。換言之,只要遵循 “嚴格模式”,就可以確保 100% 解決視圖調(diào)用的一致性問題(安全性等價于基于函數(shù)式編程思想的 Jetpack Compose),避免在多布局等背景下滋生的各種 null 安全情況的發(fā)生。
關(guān)于 “數(shù)據(jù)驅(qū)動” 的本質(zhì),可詳見 《從 被誤解 到 真香 的 Jetpack DataBinding!》 和 《是 事關(guān)軟件工程安全 的 數(shù)據(jù)驅(qū)動 UI 框架 上車指南》 中全網(wǎng)獨家提供的深度解析。
?
TOP 3:為什么 MainActivityViewModel 中使用 LiveData 綁定視圖狀態(tài),而其他 State-ViewModel 使用 ObservableField?
解答:ObservaleField 有防抖的特點,要記住這個特點,然后根據(jù)情況選擇使用。
比如 PureMusic 中通知抽屜打開,用 ObservaleField<Boolean> 不合適,而 LiveData 合適,
因為 ObservaleField 防抖,第一次 set true,就有 true 為 value 了,第二次再 set true,就不 notify 視圖刷新了(具體見 ObservaleBoolean 的 set 方法實現(xiàn))
防抖可以避免重復刷新 以減少不必要的性能開銷,所以看情況選擇 ObservaleField 或 LiveData。
更多細節(jié)內(nèi)容詳見 《從 被誤解 到 真香 的 Jetpack DataBinding!》 文末及評論區(qū)中的補充。
?
TOP 4:LiveData observe 回調(diào)走了多次,該如何處理?
解答:(注意此處所指的情況不同于 “數(shù)據(jù)倒灌”)
考慮到此前有多位小伙伴私下詢問過 LiveData “重復回調(diào)”的問題,這里額外做個明示:
LiveData 是被設(shè)計為,支持從 ViewModel、單例等唯一可信源 完成數(shù)據(jù)的一對多分發(fā),因而其內(nèi)部的觀察套路 并非 “一對一”的 觀察者模式,而是 “一對多” 的 發(fā)布-訂閱模式,我在 2018 年初自主設(shè)計并開源的 VIABUS 架構(gòu) 也是采取這種模式,內(nèi)部通過 Map 來維護訂閱者。
所以正常情況下,對于 一個 LiveData 實例,在同一個頁面中只該注冊一次觀察、請勿在 RecyclerView Adapter 的 onBindViewHolder 等處注冊,避免導致重復注冊多個訂閱者,從而不可預期地在每次請求后 “收到多次推送”。
更多完整的提示可參見 《LiveData 鮮為人知的 身世背景 和 獨特使命》 文末的最新補充。
?
TOP 5:將《最佳實踐》的 Navigation 修改版引入到自己項目,結(jié)果還是走的 replace,怎么辦?
解答:請移除自己項目中引入的 navigation.fragment gradle 引用,不然可能會覆蓋來自 architecture module 下的那些。
并且,請確保 navigation.fragment 被移入自己項目時,和原來 architecture module 中一樣,使用完整的 com.androidX 的包名路徑。
?
版權(quán)聲明
本文以 CC 署名-非商業(yè)性使用-禁止演繹 4.0 國際協(xié)議 發(fā)行。
Copyright ? 2019-present KunMinX
本文引言、思路及結(jié)論屬于作者 KunMinX 原創(chuàng),當您借鑒或引用本文的引言、思路、結(jié)論進行二次創(chuàng)作,或全文轉(zhuǎn)載時,須注明鏈接出處,否則我們保留追責的權(quán)利。