View 和 ViewModel分配責(zé)任理想情況下,ViewModel 應(yīng)該對(duì) Android 世界一無(wú)所知。這提升了可測(cè)試性,內(nèi)存泄漏安全性,并且便于模塊化。
條件語(yǔ)句,循環(huán)和通用邏輯應(yīng)該放在應(yīng)用的 ViewModel 或者其它層來(lái)執(zhí)行,而不是在 Activity 和 Fragment 中。
ViewModel 中的 View 引用ViewModel 和 Activity/Fragment 向 ViewModel 傳遞 View(Activity/Fragment) 的引用是一個(gè)很大的冒險(xiǎn)。假設(shè) ViewModel 請(qǐng)求網(wǎng)絡(luò),稍后返回?cái)?shù)據(jù)。
在 ViewModel 和 View 中通信的建議方式是觀察者模式,使用 LiveData 或者其他類(lèi)庫(kù)中的可觀察對(duì)象。 觀察者模式在 Android 中設(shè)計(jì)表示層的一種非常方便的方法是讓 View 觀察和訂閱 ViewModel(中的變化)。
private void subscribeToModel() { // Observe product data viewModel.getObservableProduct().observe(this, new Observer<Product>() { @Override public void onChanged(@Nullable Product product) { mTitle.setText(product.title); } }); }
胖 ViewModel無(wú)論是什么讓你選擇分層,這總是一個(gè)好主意。如果你的 ViewModel 擁有大量的代碼,承擔(dān)了過(guò)多的責(zé)任,那么:
使用數(shù)據(jù)倉(cāng)庫(kù)如 應(yīng)用架構(gòu)指南 中所說(shuō),大部分 App 有多個(gè)數(shù)據(jù)源:
在你的應(yīng)用中擁有一個(gè)數(shù)據(jù)層是一個(gè)好主意,它和你的視圖層完全隔離。保持緩存和數(shù)據(jù)庫(kù)與網(wǎng)絡(luò)同步的算法并不簡(jiǎn)單。建議使用單獨(dú)的 Repository 類(lèi)作為處理這種復(fù)雜性的單一入口點(diǎn). 如果你有多個(gè)不同的數(shù)據(jù)模型,考慮使用多個(gè) Repository 倉(cāng)庫(kù)。
處理數(shù)據(jù)狀態(tài)考慮下面這個(gè)場(chǎng)景:你正在觀察 ViewModel 暴露出來(lái)的一個(gè) LiveData,它包含了需要顯示的列表項(xiàng)。那么 View 如何區(qū)分?jǐn)?shù)據(jù)已經(jīng)加載,網(wǎng)絡(luò)錯(cuò)誤和空集合?
保存 activity 狀態(tài)當(dāng) activity 被銷(xiāo)毀或者進(jìn)程被殺導(dǎo)致 activity 不可見(jiàn)時(shí),重新創(chuàng)建屏幕所需要的信息被稱為 activity 狀態(tài)。屏幕旋轉(zhuǎn)就是最明顯的例子,如果狀態(tài)保存在 ViewModel 中,它就是安全的。 但是,你可能需要在 ViewModel 也不存在的情況下恢復(fù)狀態(tài),例如當(dāng)操作系統(tǒng)由于資源緊張殺掉你的進(jìn)程時(shí)。 為了有效的保存和恢復(fù) UI 狀態(tài),使用 詳見(jiàn):ViewModels: Persistence, onSaveInstanceState(), Restoring UI EventEvent 指只發(fā)生一次的事件。ViewModel 暴露出的是數(shù)據(jù),那么 Event 呢?例如,導(dǎo)航事件或者展示 Snackbar 消息,都是應(yīng)該只被執(zhí)行一次的動(dòng)作。 LiveData 保存和恢復(fù)數(shù)據(jù),和 Event 的概念并不完全符合??纯淳哂邢旅孀侄蔚囊粋€(gè) ViewModel: LiveData<String> snackbarMessage = new MutableLiveData<>(); Activity 開(kāi)始觀察它,當(dāng) ViewModel 結(jié)束一個(gè)操作時(shí)需要更新它的值: snackbarMessage.setValue("Item saved!"); Activity 接收到了值并且顯示了 SnackBar。顯然就應(yīng)該是這樣的。 但是,如果用戶旋轉(zhuǎn)了手機(jī),新的 Activity 被創(chuàng)建并且開(kāi)始觀察。當(dāng)對(duì) LiveData 的觀察開(kāi)始時(shí),新的 Activity 會(huì)立即接收到舊的值,導(dǎo)致消息再次被顯示。 與其使用架構(gòu)組件的庫(kù)或者擴(kuò)展來(lái)解決這個(gè)問(wèn)題,不如把它當(dāng)做設(shè)計(jì)問(wèn)題來(lái)看。我們建議你把事件當(dāng)做狀態(tài)的一部分。
ViewModel 的泄露得益于方便的連接 UI 層和應(yīng)用的其他層,響應(yīng)式編程在 Android 中工作的很高效。LiveData 是這個(gè)模式的關(guān)鍵組件,你的 Activity 和 Fragment 都會(huì)觀察 LiveData 實(shí)例。 LiveData 如何與其他組件通信取決于你,要注意內(nèi)存泄露和邊界情況。如下圖所示,視圖層(Presentation Layer)使用觀察者模式,數(shù)據(jù)層(Data Layer)使用回調(diào)。 當(dāng)用戶退出應(yīng)用時(shí),View 不可見(jiàn)了,所以 ViewModel 不需要再被觀察。如果數(shù)據(jù)倉(cāng)庫(kù) Repository 是單例模式并且和應(yīng)用同作用域,那么直到應(yīng)用進(jìn)程被殺死,數(shù)據(jù)倉(cāng)庫(kù) Repository 才會(huì)被銷(xiāo)毀。 只有當(dāng)系統(tǒng)資源不足或者用戶手動(dòng)殺掉應(yīng)用這才會(huì)發(fā)生。如果數(shù)據(jù)倉(cāng)庫(kù) Repository 持有 ViewModel 的回調(diào)的引用,那么 ViewModel 將會(huì)發(fā)生內(nèi)存泄露。 如果 ViewModel 很輕量,或者保證操作很快就會(huì)結(jié)束,這種泄露也不是什么大問(wèn)題。但是,事實(shí)并不總是這樣。理想情況下,只要沒(méi)有被 View 觀察了,ViewModel 就應(yīng)該被釋放。 你可以選擇下面幾種方式來(lái)達(dá)成目的:
數(shù)據(jù)倉(cāng)庫(kù)中的 LiveData為了避免 ViewModel 泄露和回調(diào)地獄,數(shù)據(jù)倉(cāng)庫(kù)應(yīng)該被這樣觀察: 當(dāng) ViewModel 被清除,或者 View 的生命周期結(jié)束,訂閱也會(huì)被清除: 如果你嘗試這種方式的話會(huì)遇到一個(gè)問(wèn)題:如果不訪問(wèn) LifeCycleOwner 對(duì)象的話,如果通過(guò) ViewModel 訂閱數(shù)據(jù)倉(cāng)庫(kù)?使用 Transformations 可以很方便的解決這個(gè)問(wèn)題。 LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> { if (repoId.isEmpty()) { return AbsentLiveData.create(); } return repository.loadRepo(repoId); } ); 在這個(gè)例子中,當(dāng)觸發(fā)更新時(shí),這個(gè)函數(shù)被調(diào)用并且結(jié)果被分發(fā)到下游。如果一個(gè) Activity 觀察了
繼承 LiveData在 ViewModel 中使用 LiveData 最常用的就是 如果你需要更多功能,繼承 LiveData 會(huì)讓你知道活躍的觀察者。這對(duì)你監(jiān)聽(tīng)位置或者傳感器服務(wù)很有用。 public class MyLiveData extends LiveData<MyData> { public MyLiveData(Context context) { // Initialize service } @Override protected void onActive() { // Start listening } @Override protected void onInactive() { // Stop listening } } 什么時(shí)候不要繼承 LiveData你也可以通過(guò)
分割線翻譯就到這里了,其實(shí)這篇文章已經(jīng)在我的收藏夾里躺了很久了。 當(dāng)時(shí)基于對(duì) MVVM 的淺薄理解寫(xiě)了一套自認(rèn)為是 MVVM 的 MVVM 架構(gòu),在閱讀一些關(guān)于架構(gòu)的文章,以及 Plaid 源碼之后,發(fā)現(xiàn)了自己的 MVVM 的一些認(rèn)知誤區(qū)。后續(xù)會(huì)對(duì) Wanandroid 應(yīng)用進(jìn)行合理改造,并結(jié)合上面譯文中提到的知識(shí)點(diǎn)作一定的說(shuō)明。歡迎 Star ! |
|
來(lái)自: 頭號(hào)碼甲 > 《待分類(lèi)》