- 原文地址:ViewModels and LiveData: Patterns + AntiPatterns
- 原文做者:Jose Alcérreca
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:boileryao
- 校對者:Zhiw miguoer
用 Architecture Components 構建的 APP 中實體的典型交互 html
理想狀況下,ViewModel 不該該知道任何關於 Android 的事情(如Activity、Fragment)。 這樣會大大改善可測試性,有利於模塊化,而且可以減小內存泄漏的風險。一個通用的法則是,你的 ViewModel 中沒有導入像 android.*
這樣的包(像 android.arch.*
這樣的除外)。這個經驗也一樣適用於 MVP 模式中的 Presenter 。前端
❌ 不要讓 ViewModel(或Presenter)直接使用 Android 框架內的類java
條件語句、循環和通常的斷定等語句應該在 ViewModel 或者應用程序的其餘層中完成,而不是在 Activity 或 Fragment 裏。視圖層一般是沒有通過單元測試的(除非你用上了 Robolectric),因此在裏面寫的代碼越少越好。View 應該僅僅負責展現數據以及發送各類事件給 ViewModel 或 Presenter。這被稱爲 Passive View 模式。(憂鬱的 View,哈哈哈)react
✅ 保持 Activity 和 Fragment 中的邏輯代碼最小化android
ViewModel 的生命週期跟 Activity 和 Fragment 不同。當 ViewModel 正在工做的時候,一個 Activity 可能處於本身 生命週期 的任何狀態。 Activity 和 Fragment 能夠被銷燬而且從新建立, ViewModel 將對此一無所知。ios
ViewModel 對配置的從新加載(好比屏幕旋轉)具備「抗性」 ↑git
把視圖層(Activity 或 Fragment)的引用傳遞給 ViewModel 是有 至關大的風險 的。假設 ViewModel 從網絡請求數據,而後因爲某些問題,數據返回的時候已經滄海桑田了。這時候,ViewModel 引用的視圖層可能已經被銷燬或者不可見了。這將產生內存泄漏甚至引發崩潰。github
❌ 避免在 ViewModel 裏持有視圖層的引用算法
推薦使用觀察者模式做爲 ViewModel 層和 View 層的通訊方式,可使用 LiveData 或者其餘庫中的 Observable 對象做爲被觀察者。數據庫
一個很方便的設計 Android 應用中的展現層的方法是讓視圖層(Activity 或 Fragment)去觀察 ViewModel 的變化。因爲 ViewModel 對 Android 一無所知,它也就不知道 Android 是多麼頻繁的幹掉視圖層的小夥伴。這樣有幾個好處:
NullPointerException
。private void subscribeToModel() {
// Observe product data
viewModel.getObservableProduct().observe(this, new Observer<Product>() {
@Override
public void onChanged(@Nullable Product product) {
mTitle.setText(product.title);
}
});
}複製代碼
Activity / Fragment 中的一個典型「訂閱」案例。
✅ 讓 UI 觀察數據的變化,而不是直接向 UI 推送數據
能減輕你的擔憂的主意必定是個好主意。若是你的 ViewModel 裏代碼太多、承擔了太多職責,試着去:
✅ 把代碼職責分散出去。若是須要的話,加上一個 Domain 層。
就像 Guide to App Architecture(應用架構指南) 裏說的那樣,大多數 APP 有多個數據源,好比:
在應用中放一個數據層是一個好主意,數據層徹底不關心展現層(MVP
中的 P
)。因爲保持緩存和數據庫與網絡同步的算法一般很瑣碎複雜,因此建議爲每一個倉庫建立一個類做爲處理同步的單一入口。
若是是許多種而且差異很大的數據模型,考慮使用多個數據倉庫。
✅ 添加數據倉庫做爲數據訪問的單一入口。
考慮一下這種狀況:你正在觀察一個 ViewModel 暴露出來的 LiveData,它包含了一個待顯示數據的列表。視圖層該如何區分被加載的數據,網絡錯誤和空列表呢?
LiveData<MyDataState>
。 MyDataState
可能包含數據是正在加載仍是已經加載成功、失敗的信息。能夠將類中有狀態和其餘元數據(好比錯誤信息)的數據封裝到一個類。參見示例代碼中的 Resource 類。
✅ 使用一個包裝類或者 LiveData 來暴露狀態信息。
Activity 的狀態是指在 Activity 消失時從新建立屏幕內容所需的信息,Activity 消失意味着被銷燬或進程被終止。旋轉屏幕是最明顯的狀況,咱們已經在 ViewModel 部分提到了。保存在 ViewModel 的狀態是安全的。
可是,你可能須要在其餘 ViewModel 也消失的場景中恢復狀態。例如,當操做系統因資源不足殺死進程時。
爲了高效地保存和恢復 UI 狀態,組合使用 onSaveInstanceState()
和 ViewModel。
這裏有個示例:ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders
咱們管只發生一次的操做叫作事件。 ViewModels 暴露數據,但對於事件怎麼樣呢?例如,導航事件或顯示 Snackbar 消息等應該僅被執行一次的操做。
事件的概念並不能和 LiveData 存取數據的方式完美匹配。來看下面這個從 ViewModel 中取出來的字段:
LiveData<String> snackbarMessage = new MutableLiveData<>();複製代碼
一個 Activity 開始觀察這個字段,ViewModel 完成了一個操做,因此須要更新消息:
snackbarMessage.setValue("Item saved!");複製代碼
顯然,Activity 接收到這個值後會顯示出來一個 SnackBar。
可是,若是用戶旋轉手機,則新的 Activity 被建立並開始觀察這個字段。當對 LiveData 的觀察開始時,Activity 會當即收到已經使用過的值,這將致使消息再次顯示!
在示例中,咱們繼承 LiveData 建立一個叫作 SingleLiveEvent 的類來解決這個問題。它僅僅發送發生在訂閱後的更新,要注意的是這個類只支持一個觀察者。
✅ 使用像 SingleLiveEvent 這樣的 observable 來處理導航欄或者 SnackBar 顯示消息這樣的狀況
響應式範例在 Android 中運行良好,它容許在 UI 和應用程序的其餘層之間創建方便的聯繫。 LiveData 是這個架構的關鍵組件,所以一般你的 Activity 和 Fragment 會觀察 LiveData 實例。
ViewModel 如何與其餘組件進行通訊取決於你,但要注意泄漏問題和邊界狀況。看下面這個圖,其中 Presenter 層使用觀察者模式,數據層使用回調:
UI 中的觀察者模式和數據層中的回凋
若是用戶退出 APP,視圖就消失了因此 ViewModel 也沒有觀察者了。若是數據倉庫是個單例或者是和 Application 的生命週期綁定的,這個數據倉庫在進程被殺掉以前都不會被銷燬。這隻會發生在系統須要資源或用戶手動殺死應用程序時,若是數據倉庫在 ViewModel 中持有對回調的引用,ViewModel 將發生暫時的內存泄漏。
Activity 已經被銷燬了可是 ViewModel 還在苟且
若是是一個輕量級 ViewModel 或能夠保證操做快速完成,這個泄漏並非什麼大問題。可是,狀況並不老是這樣。理想狀況下,ViewModels 在沒有任何觀察者的狀況下不該該持有 ViewModel 的引用:
實現這種機制有不少方法:
✅ 考慮邊界狀況,泄漏以及長時間的操做會對架構中的實例帶來哪些影響。
❌ 不要將保存原始狀態和數據相關的邏輯放在 ViewModel 中。任何從 ViewModel 所作的調用均可能是數據相關的。
爲了不泄露 ViewModel 和回調地獄(嵌套的回凋造成的「箭頭」代碼),能夠像這樣觀察數據倉庫:
當 ViewModel 被移除或者視圖的生命週期結束,訂閱被清除:
若是嘗試這種方法,有個問題:若是沒法訪問 LifecycleOwner ,如何從 ViewModel 中訂閱數據倉庫呢? 使用 Transformations 是個很簡單的解決方法。 Transformations.switchMap
容許你建立響應其餘 LiveData 實例的改變的 LiveData ,它還容許在調用鏈上傳遞觀察者的生命週期信息:
LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> {
if (repoId.isEmpty()) {
return AbsentLiveData.create();
}
return repository.loadRepo(repoId);
}
);複製代碼
在這個例子中,當觸發器獲得一個更新時,該函數被調用而且結果被分發到下游。 當一個 Activity 觀察到repo
時,相同的 LifecycleOwner 將用於 repository.loadRepo(id)
調用。
✅ 當須要在 ViewModel 中須要 Lifecycle 對象時,使用 Transformation 多是個好辦法。
LiveData 最多見的用例是在 ViewModel 中使用 MutableLiveData
而且將它們暴露爲 LiveData
來保證觀察者不會改變他們。
若是你須要更多功能,擴展 LiveData 會讓你知道何時有活躍的觀察者。例如,當想要開始監聽位置或傳感器服務時,這將頗有用。
public class MyLiveData extends LiveData<MyData> {
public MyLiveData(Context context) {
// Initialize service
}
@Override
protected void onActive() {
// Start listening
}
@Override
protected void onInactive() {
// Stop listening
}
}複製代碼
使用 onActive()
來啓動加載數據的服務是能夠的,可是若是你沒有一個很好的理由這樣作的話就不要這樣作,沒有必要非得等到 LiveData 開始被觀察才加載數據。一些通用的模式是這樣的:
start()
方法,並儘早調用這個方法。 (參見Blueprints example )❌ 一般不用拓展 LiveData。可讓 Activity 或 Fragment 告訴 ViewModel 何時開始加載數據。
[^是否須要關於 Architecture Component 的其餘任何主題的指導(或意見)?留下評論!]:
感謝 Lyla Fujiwara、Daniel Galpin、Wojtek Kaliciński 和 Florina Muntenescu。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。