原文做者: Jose Alcérreca原文地址: ViewModels and LiveData: Patterns + AntiPatternshtml
譯者:秉心說java
理想狀況下,ViewModel 應該對 Android 世界一無所知。這提高了可測試性,內存泄漏安全性,而且便於模塊化。
一般的作法是保證你的 ViewModel 中沒有導入任何 android.*
,android.arch.*
(譯者注:如今應該再加一個 androidx.lifecycle
)除外。
這對 Presenter(MVP) 來講也同樣。android
❌ 不要讓 ViewModel 和 Presenter 接觸到 Android 框架中的類
條件語句,循環和通用邏輯應該放在應用的 ViewModel 或者其它層來執行,而不是在 Activity 和 Fragment 中。
View 一般是不進行單元測試的,除非你使用了 Robolectric,因此其中的代碼越少越好。
View 只須要知道如何展現數據以及向 ViewModel/Presenter 發送用戶事件。這叫作 Passive View 模式。git
✅ 讓 Activity/Fragment 中的邏輯儘可能精簡
ViewModel 和 Activity/Fragment
具備不一樣的做用域。當 Viewmodel 進入 alive 狀態且在運行時,activity 可能位於 生命週期狀態 的任何狀態。
Activitie 和 Fragment 能夠在 ViewModel 無感知的狀況下被銷燬和從新建立。github
向 ViewModel 傳遞 View(Activity/Fragment) 的引用是一個很大的冒險。假設 ViewModel 請求網絡,稍後返回數據。
若此時 View 的引用已經被銷燬,或者已經成爲一個不可見的 Activity。這將致使內存泄漏,甚至 crash。算法
❌ 避免在 ViewModel 中持有 View 的引用
在 ViewModel 和 View 中通訊的建議方式是觀察者模式,使用 LiveData 或者其餘類庫中的可觀察對象。數據庫
在 Android 中設計表示層的一種很是方便的方法是讓 View 觀察和訂閱 ViewModel(中的變化)。
因爲 ViewModel 並不知道 Android 的任何東西,因此它也不知道 Android 是如何頻繁的殺死 View 的。
這有以下好處:編程
private void subscribeToModel() { // Observe product data viewModel.getObservableProduct().observe(this, new Observer<Product>() { @Override public void onChanged(@Nullable Product product) { mTitle.setText(product.title); } }); }
✅ 讓 UI 觀察數據的變化,而不是把數據推送給 UI
不管是什麼讓你選擇分層,這老是一個好主意。若是你的 ViewModel 擁有大量的代碼,承擔了過多的責任,那麼:segmentfault
移除一部分邏輯到和 ViewModel 具備一樣做用域的地方。這部分將和應用的其餘部分進行通訊並更新設計模式
ViewModel 持有的 LiveData。
✅ 分發責任,若是須要的話,添加 domain 層
如 應用架構指南 中所說,大部分 App 有多個數據源:
在你的應用中擁有一個數據層是一個好主意,它和你的視圖層徹底隔離。保持緩存和數據庫與網絡同步的算法並不簡單。建議使用單獨的 Repository 類做爲處理這種複雜性的單一入口點.
若是你有多個不一樣的數據模型,考慮使用多個 Repository 倉庫。
✅ 添加數據倉庫做爲你的數據的單一入口點。
考慮下面這個場景:你正在觀察 ViewModel 暴露出來的一個 LiveData,它包含了須要顯示的列表項。那麼 View 如何區分數據已經加載,網絡錯誤和空集合?
LiveData<MyDataState>
,MyDataState
能夠包含數據正在加載,已經加載完成,發生錯誤等信息。✅ 使用包裝類或者另外一個 LiveData 來暴露數據的狀態信息
當 activity 被銷燬或者進程被殺致使 activity 不可見時,從新建立屏幕所須要的信息被稱爲 activity 狀態。屏幕旋轉就是最明顯的例子,若是狀態保存在 ViewModel 中,它就是安全的。
可是,你可能須要在 ViewModel 也不存在的狀況下恢復狀態,例如當操做系統因爲資源緊張殺掉你的進程時。
爲了有效的保存和恢復 UI 狀態,使用 onSaveInstanceState()
和 ViewModel 組合。
詳見:[ViewModels: Persistence, onSaveInstanceState(), Restoring UI
State and Loaders](https://medium.com/google-dev... 。
Event 指只發生一次的事件。ViewModel 暴露出的是數據,那麼 Event 呢?例如,導航事件或者展現 Snackbar 消息,都是應該只被執行一次的動做。
LiveData 保存和恢復數據,和 Event 的概念並不徹底符合。看看具備下面字段的一個 ViewModel:
LiveData<String> snackbarMessage = new MutableLiveData<>();
Activity 開始觀察它,當 ViewModel 結束一個操做時須要更新它的值:
snackbarMessage.setValue("Item saved!");
Activity 接收到了值而且顯示了 SnackBar。顯然就應該是這樣的。
可是,若是用戶旋轉了手機,新的 Activity 被建立而且開始觀察。當對 LiveData 的觀察開始時,新的 Activity 會當即接收到舊的值,致使消息再次被顯示。
與其使用架構組件的庫或者擴展來解決這個問題,不如把它當作設計問題來看。咱們建議你把事件當作狀態的一部分。
把事件設計成狀態的一部分。更多細節請閱讀 LiveData with SnackBar,Navigation and other events (the SingleLiveEvent case)
得益於方便的鏈接 UI 層和應用的其餘層,響應式編程在 Android 中工做的很高效。LiveData 是這個模式的關鍵組件,你的 Activity 和 Fragment 都會觀察 LiveData 實例。
LiveData 如何與其餘組件通訊取決於你,要注意內存泄露和邊界狀況。以下圖所示,視圖層(Presentation Layer)使用觀察者模式,數據層(Data Layer)使用回調。
當用戶退出應用時,View 不可見了,因此 ViewModel 不須要再被觀察。若是數據倉庫 Repository 是單例模式而且和應用同做用域,那麼直到應用進程被殺死,數據倉庫 Repository 纔會被銷燬。 只有當系統資源不足或者用戶手動殺掉應用這纔會發生。若是數據倉庫 Repository 持有 ViewModel 的回調的引用,那麼 ViewModel 將會發生內存泄露。
若是 ViewModel 很輕量,或者保證操做很快就會結束,這種泄露也不是什麼大問題。可是,事實並不老是這樣。理想狀況下,只要沒有被 View 觀察了,ViewModel 就應該被釋放。
你能夠選擇下面幾種方式來達成目的:
✅ 考慮邊界狀況,內存泄露和耗時任務會如何影響架構中的實例。❌ 不要在 ViewModel 中進行保存狀態或者數據相關的核心邏輯。 ViewModel 中的每一次調用均可能是最後一次操做。
爲了不 ViewModel 泄露和回調地獄,數據倉庫應該被這樣觀察:
當 ViewModel 被清除,或者 View 的生命週期結束,訂閱也會被清除:
若是你嘗試這種方式的話會遇到一個問題:若是不訪問 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(repoId)
的調用上。
不管何時你在 ViewModel 內部須要一個 LifeCycle 對象時, Transformation 都是一個好方案。
在 ViewModel 中使用 LiveData 最經常使用的就是 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 被觀察。下面這些通用的設計模式:
ViewModel
添加 start()
方法,並儘快調用它。見 [Blueprints example]你並不須要常常繼承 LiveData 。讓 Activity 和 Fragment 告訴 ViewModel 何時開始加載數據。
翻譯就到這裏了,其實這篇文章已經在個人收藏夾裏躺了好久了。
最近 Google 重寫了 Plaid 應用,用上了一系列最新技術棧, AAC,MVVM, Kotlin,協程 等等。這也是我很喜歡的一套技術棧,以前基於此開源了 Wanandroid 應用 ,詳見 真香!Kotlin+MVVM+LiveData+協程 打造 Wanandroid! 。
當時基於對 MVVM 的淺薄理解寫了一套自認爲是 MVVM 的 MVVM 架構,在閱讀一些關於架構的文章,以及 Plaid 源碼以後,發現了本身的 MVVM 的一些認知誤區。後續會對 Wanandroid 應用進行合理改造,並結合上面譯文中提到的知識點做必定的說明。歡迎 Star !
文章首發微信公衆號:秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解。更多最新原創文章,掃碼關注我吧!