當前位置:
首頁 > 知識 > 安卓 MVVM 之禪

安卓 MVVM 之禪

我之前在多個 Android 應用中採用過多種途徑來實現 MVP 設計模式,並且過程中經歷了反覆迭代。在歷經多個項目後,我決定嘗試以 Android Data Binding 類庫為基礎來實現 MVVM。這次嘗試彷彿讓我陷入了Android 編程的極樂世界一般。

在帶你嘗試這些讓我涅槃的步驟之前,我想先與你分享我在之前給自己設定的一些目標:

一個 MVVM 單元應當僅由 ViewModel(VM)、ViewModel 的狀態(M)以及一個綁定的布局資源文件(V)構成。

MVVM 單元應當是模塊化的,並且支持嵌套。每個 MVVM 單元應支持包含一個或多個子單元,其中每個子單元仍可能包含自己的子單元。

不需要擴展 Activity類、Fragment類,或者自定義視圖。

每個 ViewModel 的行為應當是可接受和可預期的,並且不依賴任何特殊的 Android 類庫。應該可以使用 Vanilla JUnit 對其進行單元測試。

ViewModel 間的關係應當通過依賴注入來實現。

應在布局文件中聲明對 ViewModel 屬性或者方法單向和雙向的數據綁定。

ViewModel 不應了解其所支持的 View 的細節。ViewModel 中不應當包含來自 theandroid.view或者 android.widgetpackages 的任何引用。

ViewModel 應當自動綁定到與其配對的 View 的生命周期,並在生命周期結束後自動解除綁定。

ViewModel 應當獨立於 Activity 的生命周期,但是當 Activity 需要的時候也可以訪問到 ViewModel。

這個模式需要支持單個或者多個 Activity 的情況。

寫在前面的話

在開始的時候,我選擇了一些不出名(但是同樣好用的)工具:用於管理依賴注入的 Toothpick,以及用於導航和管理棧回退(back-stack)的 Okuki(我自己寫的)。我猜別人可能喜歡使用 Dagger 來管理依賴注入(DI),也可能喜歡使用 Intents、EnentBus 來完成導航功能,甚至於使用自定義的導航管理機制。你也可能傾向於使用 Activity 和 Fragments 來進行棧回退的管理。以上完全取決於個人。我僅推薦你遵循中心化和松耦合的原則來實現上述功能。只要保證這兩個原則不變,採用了什麼設計模式,如 MVP、MVVM,還是其他 UI 框架都不重要。

在文章最後包含了一種建議的棧回退的管理方式:FragmentManager。

基礎 ViewModel 及其生命周期

接下來的步驟里,為了實現依賴注入、導航和棧回退,我定義了一個 ViewModel 基礎介面,並規定了附加、分離相關 View 生命周期的方法。

首先我定義了一個 ViewModel 介面:

下一步,我使用了 data binding 庫中的 View.OnAttachStateListener 來實現綁定,然後將 android:onViewAttachedToWindow 和 android:onViewDetachedFromWindow 映射到我的 ViewModel 類的對應方法當中。我實現了這些方法,並將其關聯到 ViewModel 介面的 onAttach 和 onDetach 方法上。通過這種方式,我可以在相應的擴展類當中隱藏所必需的 View 參數。此外,我還在 View 的生命周期中集成了依賴注入和 Rx 自動訂閱機制。

我實現的 ViewModel 基礎類:

現在,就可以直接使用該基類的任意 ViewModel 擴展了。你只需要將相應的 ViewModel 綁定到這個布局當中,同時把附加、分離屬性映射到根 ViewGroup 即可。就像下面這樣:

模塊化單元

到現在,我已經能夠實現將 ViewModel 綁定到一個視圖以及視圖的生命周期。下一步我需要一種一致的、模塊化的方式將 MVVM 單元載入到容器當中。首先我定義了一個介面,在這個介面中規定了 ViewModel 和布局資源的關聯關係。

接下來,我在 MvvmComponent 中定義了一個自定義的數據綁定關係。這個綁定幫助完成布局的渲染、ViewModel 的綁定,並載入到一個 ViewGroup 當中。

需要注意的是,我在渲染的過程中將 attachToParent 參數設置為 false,然後在綁定完成後通過顯式地執行 addView(view) 方法來完成附加。我這樣做的原因是為了 ViewModel 的 onViewAttachedToWindow 方法能夠正常被調用,因為這個方法需要 View 在渲染之前就綁定 ViewModel。

現在我可以使用新的綁定關係了。在我的布局文件中,我通過新增 component 屬性的方式來添加一個 ViewGroup 容器。

我通過使用 ObservableField 來在我的 ViewModel 中提供斷開組件的方式。

組件類本身通過對父 ViewModel 的調用,提取出了資源 ID 和子 ViewModel 的定義,並且在父 ViewModel 傳遞過來的數據中,只接受那些子 ViewModel 初始化過程需要的參數。

到現在,子組件可以輕鬆在 ViewModel 狀態的基礎上載入。而這個過程並不需要 ViewModel 對布局、View 或者其他 ViewModel 有任何的了解。

Activity 生命周期

按照開始的計劃,我的 MVVM 單元獨立於 Activity 生命周期之外。但有時候我們又需要訪問它。我們可以通過在 Bundle 實例中保存、恢復的方式來實現,也可以通過實現對暫停、恢復事件的響應的辦法來完成。這些都可以根據實際需求來選擇,並且比較簡單。只需要把這些事件委託給一個繼承了 Application.ActivityLifecycleCallbacks 的單例類,就能實現。當然這個單例類需要註冊到當前應用之上。這樣這個單例類就能通過 Listeners 或者 Observables 來暴露出這些事件,並把他們注入到任何需要響應這些事件的 ViewModel當中。

使用 Fragments 完成棧回退

我在本帖一開始就提到過,我的棧回退是通過自定義的庫來實現的。但是僅需要一些簡單的改動,你就能將其替換為 Android 自帶的 FragmentManager。為了實現這個目標,需要向 MvvmComponent 介面中增加額外的方法:

下一步,創建一個 Fragment 來對你的 MVVM 單元進行包裝,像下面這樣:

注意布局文件中需要聲明 fm 數據變數,並且將其設置為 ViewGroup 容器的屬性。同時,需要關注的還有:配置變化時造成的關聯影響、layoutResId 進程僵死,以及你的 MvvmFragment 的 vm 成員屬性。適當的調整你的 Fragment 參數也很有必要。

現在你可以通過修改自定義組件的方式來使用你的 MvvmFragment,而不是直接渲染並綁定 ViewModel。

示例應用

如果你想參考一個完整的、使用 MVVM 來實現的(沒有 Fragments)應用示例,可以在 這裡 參考我的例子。

編程愉快!

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 優才學院 的精彩文章:

一日是戰狼,終身是戰狼
PHP工程師標準
數據結構淺析:鏈表
ES6:模板字元串
超全面!聊天機器人的界面交互設計實戰經驗總結

TAG:優才學院 |

您可能感興趣

VIVOnex登頂安卓機皇
MOTO RAZR V3s渲染圖曝光:全功能安卓機
《VR男友》來了?I社推出VR新作,免費登陸iOS和安卓平台
諾基亞7 PLUS曝光,HMD欲再造安卓旗艦
安卓端滲透工具DVHMA:自帶漏洞的混合模式APP
12GB RAM+1TB ROM:最高配置安卓手機即將面世!
VRMark推出安卓版:可測試手機VR性能
國產安卓系統的三分天下:對比EMUI、MIUI和Flyme
CODESYS平台,工控界的安卓
4K 電視+安卓TV 飛利浦今年將這樣亮相 CES
谷歌將在MWC發布安卓Oreo(Go版本)和安卓One機型
氫OS超MIUI成最流暢安卓系統,華為EMUI逆襲,米粉不服!
MIUI、Flyme、EMUI,哪個代表了國產安卓系統的最高水平?
社交VR平台vTime宣布增加AR模式,支持iOS及安卓設備
小米MIUI、魅族Flyme、華為EMUI,哪個代表了國產安卓系統的最高水平?
首款運行在安卓手機上的SWITCH模擬器MONONX
安卓手機ROM和RAM到底有什麼區別?
VIVO NEX 會是成為下巴最窄的安卓機?聯想Z5表示不服
MMOARPG《龍族血統》、漫改RPG《太乙仙魔錄之靈飛紀》明日安卓開測
亞馬遜AR View現已支持安卓設備