一、Jetpack作喘,面向標準化的開發(fā)

面向標準化開發(fā)已成現(xiàn)實

金三銀四理疙,相信有不少讀者在抓緊機會面試。

Android 市場已今非昔比徊都。在過去沪斟,迫于招人的壓力,應(yīng)試者只需了解四大組件暇矫、視圖主之、網(wǎng)絡(luò)請求,即可謀得一份滿意的工作李根。

現(xiàn)如今槽奕,Jetpack 架構(gòu)組件 及 標準化開發(fā)模式 的確立,意味著 Android 開發(fā)已步入成熟階段:

許多 樣板代碼 不再需要開發(fā)者手寫房轿,而是可以通過模版工具 自動生成粤攒,在取締繁雜耗時的重復(fù)工作的同時,避免因人工操作的疏忽囱持,而造成難以排查夯接、不可預(yù)期的錯誤

這十分符合企業(yè)的利益纷妆,因而面試官在招人的時候盔几,也更加看重應(yīng)試者對 架構(gòu)組件 —— 至少是 MVVM 的理解程度。??

像“解耦”等 含糊其辭的說法掩幢,已經(jīng)不能夠被面試官所認可逊拍,稍微對 MVVM 有一點經(jīng)驗的面試官都會請你舉例說明,好證明你確實對 MVVM 有著正確际邻、深入的理解芯丧,能夠自然而然地寫出標準化、規(guī)范化的代碼世曾,能夠迅速適應(yīng) 各家公司自制的 自動化模版工具缨恒。

本文的目標

本人擁有 3 年的 移動端架構(gòu) 踐行和設(shè)計經(jīng)驗,領(lǐng)導(dǎo)團隊重構(gòu)的中大型項目多達十數(shù)個轮听,對 Jetpack MVVM 架構(gòu)在確立規(guī)范化骗露、標準化 開發(fā)模式 以減少不可預(yù)期的錯誤 所作的努力,有著深入的理解蕊程。

因而本文的目標,就是結(jié)合前幾期我們分別 深入淺出 介紹過的 Lifecycle驼唱、LiveData藻茂、ViewModel、DataBinding,來融匯貫通地演繹一下:

作為 應(yīng)用開發(fā)骨架 的 標準化狀態(tài)管理框架辨赐,究竟為 快速開發(fā)過程中 減少不可預(yù)期的錯誤 做了哪些努力优俘。

不同于 東拼西湊、人云亦云掀序、徒添困擾 的網(wǎng)文帆焕,愿意將 標準化開發(fā)模式的 深度思考知識實戰(zhàn)反思經(jīng)驗 無保留地分享,全網(wǎng)僅此一家不恭。這樣的文章可以說是 看一篇叶雹、少一篇,因此换吧,就算不去 hold 住面試官折晦,也請務(wù)必跟隨本文的腳步,無障礙地將 Jetpack MVVM 過一遍沾瓦!??

文章目錄一覽

  • 前言

  • 面向標準化開發(fā)已成現(xiàn)實

  • 本文的目標

  • Jetpack Lifecycle

    • Lifecycle 存在前的混沌世界

    • Lifecycle 為什么能解決上述這些問題满着?

  • Jetpack LiveData

    • LiveData 存在前的混沌世界

    • LiveData 為什么能解決上述這些問題?

    • LiveData 有個坑需要注意

  • Jetpack ViewModel

    • ViewModel 存在前的混沌世界

    • ViewModel 為什么能做到這幾點贯莺?

  • Jetpack DataBinding

    • DataBinding 存在前的混沌世界

    • DataBinding 就是來解決這些問題

  • 綜上

Jetpack Lifecycle

Lifecycle 的存在风喇,主要是為了解決 生命周期管理 的一致性問題

Lifecycle 存在前的混沌世界

在 Lifecycle 面市前,生命周期管理 純靠手工維持缕探,這樣就容易滋生大量的一致性問題魂莫。

例如跨頁面共享的 GpsManager 組件,在每個依賴它的 Activity 的 onResume 和 onPause 中都需要 手工 激活撕蔼、解綁 和 叫停豁鲤。

那么 隨著 Activity 的增多,這種手工操作 埋下的一致性隱患 就會指數(shù)級增長

一方面鲸沮,凡是手工維持的琳骡,開發(fā)者容易疏忽,特別是工作交接給其他同事時讼溺,同事并不能及時注意到這些細節(jié)楣号。

另一方面,分散的代碼不利于修改怒坯,日后除了激活炫狱、叫停,若有其他操作需要補充(例如狀態(tài)監(jiān)聽)剔猿,那么每個 Activity 都需要額外書寫一遍视译。

image

Lifecycle 為什么能解決上述這些問題?

Lifecycle 通過 模板方法模式 和 觀察者模式归敬,將生命周期管理的復(fù)雜操作酷含,全部在作為 LifecycleOwner 的基類中(例如視圖控制器的基類)封裝好鄙早,默默地在背后為開發(fā)者運籌帷幄,

開發(fā)者因而得以在視圖控制器(子類)中只需一句 getLifecycle().addObserver(GpsManager.getInstance) 椅亚,優(yōu)雅地完成 第三方組件在自己內(nèi)部 對 LifecycleOwner 生命周期的感知限番。

image

除了解決一致性問題,這樣做還 順帶地提供了其他 2 個好處

1.規(guī)避 為監(jiān)聽狀態(tài) 而 注入視圖控制器 的做法

當需要監(jiān)聽狀態(tài)時呀舔,以往我們的做法是 通過方法手工注入 Activity 等參數(shù)弥虐,這埋下了內(nèi)存泄漏的隱患 —— 因為團隊中的新手容易因這是個 Activity,而在日后誤將其依賴給組件中的其他成員媚赖。

現(xiàn)如今霜瘪,我們可以直接在組件內(nèi)部 點到為止 地監(jiān)聽 LifecycleOwner 的狀態(tài),從而規(guī)避這種不恰當?shù)氖褂谩?/p>

2.規(guī)避 為追溯事故來源 而 注入視圖控制器 的做法

當發(fā)生事故時省古,以往我們?nèi)粝朐诮M件中 追溯事故來源粥庄,同樣不得不從方法中直接注入 Activity 等,這同樣埋下了內(nèi)存泄漏的隱患〔蚣耍現(xiàn)如今組件因?qū)崿F(xiàn)了 DefaultLifecycleObserver惜互,而得以通過生命周期回調(diào)方法中的 LifecycleOwner 參數(shù),在方法作用域中 即可得知事故來源琳拭,無需更多帶有隱患的操作训堆。

如果這么說還不理解的話,可具體參考我在 《為你還原一個真實的 Jetpack Lifecycle》 中提供的 GpsManager 案例白嘁,本文不再累述坑鱼。

Jetpack LiveData

LiveData 的存在,主要是為了幫助 新手老手 都能不假思索地遵循 通過唯一可信源分發(fā)狀態(tài) 的標準化開發(fā)理念絮缅,從而使在快速開發(fā)過程中 難以追溯鲁沥、難以排查、不可預(yù)期 的問題所發(fā)生的概率降低到最小耕魄。

LiveData 存在前的混沌世界

在 LiveData 面市前画恰,我們分發(fā)狀態(tài),多是通過 EventBus 或 Java Interface 來完成的吸奴。不管你是用于網(wǎng)絡(luò)請求回調(diào)的情況允扇,還是跨頁面通信的情況。

那這造成了什么問題呢则奥?首先考润,EventBus 只是純粹的 Bus,它 缺乏上述提到的 標準化開發(fā)理念 的約束读处,那么人們在使用這個框架時糊治,容易因 去中心化 地濫用,而造成 諸如 毫無防備地收到 預(yù)期外的 不明來源的推送罚舱、拿到過時的數(shù)據(jù) 及 事件源追溯復(fù)雜度 為 n2 的局面井辜。

并且揖赴,EventBus 本身缺乏 Lifecycle 的加持,存在生命周期管理的一致性問題抑胎。這是 EventBus 的硬傷,也是我拒絕使用 EventBus 的最主要因素渐北。

對上述狀況不理解的阿逃,可具體參考我在 《LiveData 鮮為人知的 身世背景 和 獨特使命》 中提供的 播放器狀態(tài)全局通知 的案例

LiveData 為什么能解決上述這些問題?

首先赃蛛,LiveData 是在 Google 希望確立 標準化恃锉、規(guī)范化 的開發(fā)模式 —— 這樣一種背景下誕生的,因而為了達成這個艱巨的 使命呕臂,Google 十分克制地將其設(shè)計為破托,僅支持狀態(tài)的輸入和監(jiān)聽,從而歧蒋,它不得不 在單例的配合下土砂,承上啟下地完成 狀態(tài) 從 唯一可信源 到 視圖控制器 的輸送

(ViewModel 姑且也算是一種單例谜洽,一種工廠模式實現(xiàn)的偽單例萝映。唯一可信源是指 生命周期獨立于 視圖控制器的 數(shù)據(jù)組件,通常是 單例 或共享 ViewModel)

這使得任何一次狀態(tài)推送阐虚,都可預(yù)期序臂、都能方便地追溯來源,而不至于在 事件追溯復(fù)雜度為 n2 的迷宮中白費時間实束。(即奥秆,無論是從哪個視圖控制器發(fā)起的 對某個共享狀態(tài)改變的請求,狀態(tài)最終的改變 都由 作為唯一可信源的 單例或 SharedViewModel 來一對多地通知改變)

image

并且咸灿,這種承上啟下的方式构订,使得單向依賴成為可能:單例無需通過 Java Interface 回調(diào)通知視圖控制器,從而規(guī)避了視圖控制器 被生命周期更長的單例 依賴 所埋下的內(nèi)存泄漏的隱患析显。

LiveData 有個坑需要注意

不過鲫咽,LiveData 的設(shè)計有個坑,這里我順帶提一下谷异。

為了在視圖控制器發(fā)生重建后分尸,能夠 自動倒灌 所觀察的 LiveData 的最后一次數(shù)據(jù),LiveData 被設(shè)計為粘性事件歹嘹。

—— 我姑且認為這是個拓展性不佳的設(shè)計箩绍,甚至可以說是一個 bug,

因為 MVVM 是一個整體尺上,既然 ViewModel 支持共享作用域材蛛,并且官方文檔都承認了通過 共享 ViewModel 來實現(xiàn)跨頁面通信的需求圆到,

那么基于 “開閉原則”,LiveData 理應(yīng)提供一個與 MutableLiveData 平級的底層支持卑吭,專門用于非粘性的事件通信的情況芽淡,否則直接在跨頁面通信中使用 MutableLiveData 必造成 事件回調(diào)的一致性問題 及 難以預(yù)期的錯誤

關(guān)于非粘性 LiveData 的實現(xiàn)豆赏,網(wǎng)上存在通過 “事件包裝類”(只適合 kotlin 的情況) 和 “反射干預(yù) LastVersion” (適用于 Java 的情況)兩種方式來解決:

juejin.im/post/5b2b1b…

blog.csdn.net/geyuecang/a…

無論是使用哪一種實現(xiàn)挣菲,我都建議 遵循傳統(tǒng) LiveData 所遵循的開發(fā)理念,通過唯一可信源分發(fā)狀態(tài)掷邦,來方便事件源頭的追溯白胀。對于 “去中心化” 的 Bus 方式,我拒絕在項目中這樣使用抚岗。

(具體我會在未來開源的最佳實踐項目中 展示 UnPeekLiveData 的使用)

Jetpack ViewModel

ViewModel 的存在或杠,主要是為了解決 狀態(tài)管理 和 頁面通信 的問題。

ViewModel 存在前的混沌世界

ViewModel 的本職工作是 狀態(tài)托管狀態(tài)管理的分治宣蔚,也即當視圖控制器重建時向抢,

對于輕量的狀態(tài),可以通過視圖控制器基類的 saveInstanceState 機制胚委,以序列化的方式完成存儲和恢復(fù)笋额。

對于重量級的狀態(tài),例如通過網(wǎng)絡(luò)請求得到的 List篷扩,可以通過生命周期長于視圖控制器的 ViewModel 持有兄猩,從而得以直接從 ViewModel 恢復(fù),而不是以效率較低的序列化方式鉴未。

在 Jetpack ViewModel 面市之前枢冤,MVP 的 Presenter 和 MVVM - Clean 的 ViewModel 都不具備狀態(tài)管理分治的能力。

Presenter 和 Clean ViewModel 的生命周期都與視圖控制器同生共死铜秆,因而它們頂多是為 DataBinding 提供狀態(tài)的托管淹真,而無法實現(xiàn)狀態(tài)的分治。

到了 Jetpack 這一版连茧,ViewModel 以精妙的設(shè)計核蘸,達成了狀態(tài)管理,以及可共享的作用域啸驯。

ViewModel 為什么能做到這幾點客扎?

其實這版主要是基于 工廠模式,使得 ViewModel 被 LifecycleOwner 所持有罚斗、通過 ViewModelProvider 來引用徙鱼,

所以 它既類似于單例: —— 當被作為 LifecycleOwner 的 Activity 持有時,能夠脫離 Activity 旗下 Fragment 的生命周期,從而實現(xiàn)作用域共享袱吆,

實際上又不是單例: —— 生命周期跟隨 作為 LifecycleOwner 的視圖控制器厌衙,當 Owner(Activity 或 Fragment)被銷毀時,它也被 clear绞绒。

此外婶希,出于對視圖控制器重建的考慮,Google 在視圖控制器基類中通過 retain 機制對 ViewModel 進行了保留蓬衡。

因此饲趋,對于 作用域共享 和 視圖重建 的情況,狀態(tài)因完好地被保留撤蟆,而得以被視圖控制器在恢復(fù)時直接使用。

再者堂污,由于存在 共享作用域的考慮家肯,所以 ViewModel 本身也承擔了跨頁面通信(例如事件回調(diào))的職責。前面在介紹 LiveData 時盟猖,對于 LiveData 在事件通信時粘性設(shè)計的問題已經(jīng)介紹過了讨衣,這里不再累述。

截至 2020.2.1式镐,ViewModel 在 Fragment 中的 retain 設(shè)計已發(fā)生劇變反镇,具體緣由可參考我在 《有了 Jetpack ViewModel . . . 真的可以為所欲為!》 文末及評論區(qū)的最新補充娘汞。

Jetpack DataBinding

DataBinding 的存在歹茶,主要是為了解決 視圖調(diào)用 的一致性問題。

DataBinding 存在前的混沌世界

在 DataBinding 面市前你弦,我們?nèi)粢淖円晥D的狀態(tài)惊豺,首先就要引用該視圖,例如 textView.setText()禽作,

這造成什么問題呢尸昧?

當頁面存在橫、豎布局旷偿,且兩種布局的控件存在差異烹俗,例如橫屏存在 textView 控件,而豎屏沒有萍程,那么我們就不得不在視圖控制器中為 textView 做判空處理幢妄,這就造成了一致性問題 —— 容易疏忽而忘記判空,畢竟頁面多達數(shù)十個茫负、每個頁面的控件也無數(shù)磁浇。

那怎么辦呢?

DataBinding 就是來解決這些問題

通過在布局中與可觀察的數(shù)據(jù)發(fā)生綁定朽褪,那么當該數(shù)據(jù)被 set 新的內(nèi)容時置吓,控件也將得到通知和刷新无虚。

換言之,在使用 DataBinding 后衍锚,唯一的改變是友题,你無需手工調(diào)用視圖來 set 新狀態(tài),你只需 set 數(shù)據(jù)本身戴质。

因而度宦,DataBinding 并非許多人不假思索認為的,將 UI 邏輯搬到 XML 中寫 從而難以調(diào)試 —— 事實根本不是這樣的:

DataBinding 只負責綁定數(shù)據(jù)告匠、負責作為 UI 邏輯末端的狀態(tài)的改變(也即它是一個不可再分的原子操作戈抄,本來就不需要調(diào)試),原本在視圖控制器中 UI 邏輯怎么寫后专,現(xiàn)在還是怎么寫划鸽,只不過不再需要 textView.setText(xxx),而是直接 xxx.set()戚哎。

所以在 DataBinding 的幫助下裸诽,好處總共有多少個呢?

1.規(guī)避了視圖狀態(tài)的 一致性問題 —— 無需手工判空型凳。

2.規(guī)避了視圖狀態(tài)的 一致性問題丈冬,乃至無需視圖調(diào)用,從而完全不用編寫 findViewById甘畅。

3.就算要調(diào)用視圖埂蕊,也不用 findViewById,而是直接通過 binding 來引用疏唾。

4.先前的 UI 邏輯基本不用改動粒梦,改的只是作為末端的狀態(tài)改變的方式。

……

此外荸实,DataBinding 有個大殺器就是匀们,能為控件提供自定義屬性的 BindingAdapter,它不僅可以解決 圓角 Drawable 復(fù)用的問題(你懂得)准给,還可以實現(xiàn) imageView 直接綁定 url 等需求泄朴,總之,沒有它辦不到的露氮,只有你想不到的祖灰,DataBinding 的好處等著你挖掘。??

關(guān)于 DataBinding 的注意事項畔规,以及屢試不爽的排坑技巧局扶,可具體參考 《從 被誤解 到 真香 的 Jetpack DataBinding!》,這里不做累述三妈。

綜上

Lifecycle 的存在畜埋,主要是為了解決 生命周期管理 的一致性問題

LiveData 的存在畴蒲,主要是為了幫助 新手老手 都能不假思索地 遵循 通過唯一可信源分發(fā)狀態(tài) 的標準化開發(fā)理念悠鞍,從而在快速開發(fā)過程中 規(guī)避一系列 難以追溯、難以排查模燥、不可預(yù)期 的問題咖祭。

ViewModel 的存在,主要是為了解決 狀態(tài)管理 和 頁面通信 的問題蔫骂。

DataBinding 的存在么翰,主要是為了解決 視圖調(diào)用 的一致性問題

它們的存在 大都是為了 在軟件工程的背景下 解決一致性的問題辽旋、將容易出錯的操作在后臺封裝好浩嫌,方便使用者快速、穩(wěn)定戴已、不產(chǎn)生預(yù)期外錯誤地編碼

這樣說锅减,你理解了嗎糖儡???

轉(zhuǎn)載自:
GitHub : Jetpack-MVVM-Best-Practice

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怔匣,隨后出現(xiàn)的幾起案子握联,更是在濱河造成了極大的恐慌,老刑警劉巖每瞒,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金闽,死亡現(xiàn)場離奇詭異,居然都是意外死亡剿骨,警方通過查閱死者的電腦和手機代芜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浓利,“玉大人挤庇,你說我怎么就攤上這事〈矗” “怎么了嫡秕?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長苹威。 經(jīng)常有香客問我昆咽,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任掷酗,我火速辦了婚禮调违,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汇在。我一直安慰自己翰萨,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布糕殉。 她就那樣靜靜地躺著亩鬼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪阿蝶。 梳的紋絲不亂的頭發(fā)上雳锋,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音羡洁,去河邊找鬼玷过。 笑死,一個胖子當著我的面吹牛筑煮,可吹牛的內(nèi)容都是我干的辛蚊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼真仲,長吁一口氣:“原來是場噩夢啊……” “哼袋马!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秸应,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤虑凛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后软啼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桑谍,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年祸挪,在試婚紗的時候發(fā)現(xiàn)自己被綠了锣披。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贿条,死狀恐怖盈罐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闪唆,我是刑警寧澤盅粪,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站悄蕾,受9級特大地震影響票顾,放射性物質(zhì)發(fā)生泄漏础浮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一奠骄、第九天 我趴在偏房一處隱蔽的房頂上張望豆同。 院中可真熱鬧,春花似錦含鳞、人聲如沸影锈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸭廷。三九已至,卻和暖如春熔吗,著一層夾襖步出監(jiān)牢的瞬間辆床,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工桅狠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讼载,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓中跌,卻偏偏與公主長得像咨堤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漩符,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354