[Android] 記一次 MVVM 實踐
背景:為什麼選擇了MVVM
公司的專案一直是以 Activity 為載體的 Android 式 MVC 架構,上手快,大多數頁面程式碼也挺容易讀的。只是某複雜業務的 Activity 會有上千行的程式碼,內部複雜的狀態判斷和非同步邏輯特別多,而且原作者早已離職,每次提測都只能祈求這裡不出 bug。
為了重構這裡的程式碼,引入 MVP 或 MVVM 是比較合適的方案。精簡原有的邏輯也可以一定程度上增加程式碼的可讀性,但還是難以避免單個 Activity 中存在大量程式碼的問題。MVP 似乎是 Android 開發中更加流行的方案(樣本數為2,兩個同事都用過),但 MVP 的問題也是可預見的。恰好 Google 在 Jetpack 的 Architecture 中又宣傳了一波 MVVM,跟大佬們商量之後,我決定先改寫一個 Activity 試試。
MVC/MVP/MVVM 的介紹就省略了,下面簡單說明一下用到的技術。
知識準備:資料繫結方法
資料繫結到 View 是 MVVM 的核心,對於不同的應用場景需要使用不同的資料繫結手段:
- databinding 是常被提及的資料繫結框架,應用於將資料直接繫結到 layout 檔案,可以有效的給 Activity 減負。
- LiveData 是另一種 Google 推薦的方案,當 LiveData 作為資料來源,既可以結合 databinding 將資料變化更新到 layout 中,也可以在 Activity 中設定 Observe 回撥,實現更多 layout 中做不到的 View 效果(比如顯示一個 dialog)。
二者都是 Google 出品,具體如何使用就不贅述了,後面會單獨把遇到的坑記錄下來。
實踐過程簡述
先簡單說一下功能吧,改動之前的程式碼就不貼了,畢竟是公司的專案。
核心流程大概是這樣的,還有一些暫停、模式切換和 postDelay 製造的延遲效果,整個流程中充滿了各種非同步操作,每個非同步操作的回撥中都需要判斷一連串的狀態,比如頁面是否在顯示中,音訊是否正在播放等。
為了改寫成 MVVM 模式的程式碼,首先要區分開哪些屬於 View 層,哪些屬於 ViewModel 層。只宣告主要流程的話,內容有以下幾點:
功能點 | View 層 | ViewModel 層 |
---|---|---|
倒計時321 | 顯示倒計時的 Dialog | 每秒更新剩餘時間(321) |
播音訊 | 顯示播放進度條(%) | 控制播放及回撥處理 |
錄音 | 顯示錄音進度條(時長等於原音訊,並非手動停止) | 控制錄音及回撥處理,錄音計時停止 |
提交資料 | 提交中顯示 loading | 發起網路請求提交資料 |
暫停 | 顯示暫停 Dialog,恢復其他 View | 停止當前操作,回到當前題目最初狀態 |
稍微分析一下,除了 Dialog 之外的 View 改動儘量使用 databinding 直接放在 layout 中,Dialog 的顯示和隱藏都由一個 Boolean 型別的 LiveData 來控制,Activity 中基本只需要寫 ViewModel/databinding 的初始化程式碼和 Dialog 相關程式碼。
ViewModel 中是全部的業務邏輯程式碼,但不持有任何 View 相關的例項,所有的改變都作用於資料即可。另外,由於 databinding 和 LiveData 都是對生命週期友好的框架,在 View 層銷燬後更新資料並不會導致崩潰,在充滿非同步操作的邏輯中可以節省很多個 if 判斷。
中途發現這個 RecyclerView 的互動太複雜了,databinding 中需要大量的 if 判斷,為了加強程式碼的可讀性,就把 adapter 相關的繫結採取 LiveData 實現了。最終 ViewModel 只有以下幾個可觀察資料:
簡單畫了一下重寫前後的主流程圖
提交後跳轉頁面的 Intent 需要傳遞多個數據,這裡用了一個 Bundle 型別的 LiveData,在 Activity 中直接使用 Intent.putExtras 把值放進去即可。同理,在 ViewModel 中初始化資料時也可以把 getIntent().getExtras() 傳過去。
踩坑記錄
1. Kotlin 與 databinding 的相容性問題以及不同 Android Studio 版本的區別。
在 Android Studio 3.2 之前,databinding 需要新增一個 kapt 的 databinding-compiler 依賴,具體的版本號也需要對應,否則編譯後無法生成 Binding 類,就會出現 cannot resolve symbol xxxxBinding 的錯誤。
在 Android Studio 3.2 之後正好相反,如果不刪除這個依賴就會報錯。
具體描述可以看這個: stackoverflow.com/questions/5…
2. databinding 和 recyclerview 怎麼合作。
直接和資料打交道的 View 很容易轉換成使用 databinding 的程式碼,但 RecyclerView 這種需要一個 adapter 的 View 就會面臨許多麻煩。github 上有些針對列表類 View 使用 databinding 的框架, 甚至可以做到不用再寫 adapter,大多數場景下是可以用的,唯一的缺點是 debug 艱難。
但是我沒選擇這種方案,當 RecyclerView 中存在複雜的互動邏輯時,使用 databinding 會產生特別多的狀態資料,反而讓程式碼更加複雜了。不改變 adapter 的寫法,可以用 LiveData 在 Activity 中把值傳到 adapter 中。
3. 只進行一次觸發的 View 操作如何處理。
大多數 Data 都表示一個狀態,比如 Dialog 是否展示,TextView 的文字內容等。但有些特殊的 View 層變化只需要一個觸發,比如 Toast 和 SnackBar。
從原則上看,ViewModel 顯然不應該處理 Toast,但 Toast 這種非常輕量又可以用 ApplicationContext 來實現的,稍微違背一下規則我個人也能接受……
當然,不能推薦大家也都這麼搞,google sample 的 architecture 專案中有一個SingleLiveEvent,可以直接複製出來用。專案地址:github.com/googlesampl…
完成後的一些個人理解
什麼時候需要 MVVM?
什麼時候都可以用。雖然對於簡單的業務邏輯來說程式碼量會增加,但是 MVVM 的思考方式會讓程式碼更容易維護。過去程式碼都寫在 Activity 中時,方法的拆解通常只考慮功能的相關程度和程式碼行數,不會刻意區分 View 和邏輯。後期經過不同的人修改 bug,就會越來越混亂。
如何推廣 MVVM
在團隊中推廣新的技術一向都不容易,好在 MVVM 學習成本不高,帶來的好處也是顯而易見的。用一點時間重寫一遍專案中別人最不願意改的頁面,然後分享一下程式碼就好了。