一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

【Medium 萬(wàn)贊好文】ViewModel 和 LIveData:模式 + 反模式

 頭號(hào)碼甲 2020-09-20

原文作者: Jose Alcérreca

原文地址: ViewModels and LiveData: Patterns + AntiPatterns

譯者:秉心說(shuō)

Typical interaction of entities in an app built with Architecture Components

View 和 ViewModel

分配責(zé)任

理想情況下,ViewModel 應(yīng)該對(duì) Android 世界一無(wú)所知。這提升了可測(cè)試性,內(nèi)存泄漏安全性,并且便于模塊化。
通常的做法是保證你的 ViewModel 中沒(méi)有導(dǎo)入任何 android.*android.arch.* (譯者注:現(xiàn)在應(yīng)該再加一個(gè) androidx.lifecycle)除外。
這對(duì) Presenter(MVP) 來(lái)說(shuō)也一樣。

? 不要讓 ViewModel 和 Presenter 接觸到 Android 框架中的類(lèi)

條件語(yǔ)句,循環(huán)和通用邏輯應(yīng)該放在應(yīng)用的 ViewModel 或者其它層來(lái)執(zhí)行,而不是在 Activity 和 Fragment 中。
View 通常是不進(jìn)行單元測(cè)試的,除非你使用了 Robolectric,所以其中的代碼越少越好。
View 只需要知道如何展示數(shù)據(jù)以及向 ViewModel/Presenter 發(fā)送用戶事件。這叫做 Passive View 模式。

? 讓 Activity/Fragment 中的邏輯盡量精簡(jiǎn)

ViewModel 中的 View 引用

ViewModel 和 Activity/Fragment
具有不同的作用域。當(dāng) Viewmodel 進(jìn)入 alive 狀態(tài)且在運(yùn)行時(shí),activity 可能位于 生命周期狀態(tài) 的任何狀態(tài)。
Activitie 和 Fragment 可以在 ViewModel 無(wú)感知的情況下被銷(xiāo)毀和重新創(chuàng)建。

ViewModels persist configuration changes

向 ViewModel 傳遞 View(Activity/Fragment) 的引用是一個(gè)很大的冒險(xiǎn)。假設(shè) ViewModel 請(qǐng)求網(wǎng)絡(luò),稍后返回?cái)?shù)據(jù)。
若此時(shí) View 的引用已經(jīng)被銷(xiāo)毀,或者已經(jīng)成為一個(gè)不可見(jiàn)的 Activity。這將導(dǎo)致內(nèi)存泄漏,甚至 crash。

? 避免在 ViewModel 中持有 View 的引用

在 ViewModel 和 View 中通信的建議方式是觀察者模式,使用 LiveData 或者其他類(lèi)庫(kù)中的可觀察對(duì)象。

觀察者模式

在 Android 中設(shè)計(jì)表示層的一種非常方便的方法是讓 View 觀察和訂閱 ViewModel(中的變化)。
由于 ViewModel 并不知道 Android 的任何東西,所以它也不知道 Android 是如何頻繁的殺死 View 的。
這有如下好處:

  1. ViewModel 在配置變化時(shí)保持不變,所以當(dāng)設(shè)備旋轉(zhuǎn)時(shí)不需要再重新請(qǐng)求資源(數(shù)據(jù)庫(kù)或者網(wǎng)絡(luò))。

  2. 當(dāng)耗時(shí)任務(wù)執(zhí)行結(jié)束,ViewModel 中的可觀察數(shù)據(jù)更新了。這個(gè)數(shù)據(jù)是否被觀察并不重要,嘗試更新一個(gè)
    不存在的 View 并不會(huì)導(dǎo)致空指針異常。

  3. ViewModel 不持有 View 的引用,降低了內(nèi)存泄漏的風(fēng)險(xiǎn)。

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 觀察數(shù)據(jù)的變化,而不是把數(shù)據(jù)推送給 UI

胖 ViewModel

無(wú)論是什么讓你選擇分層,這總是一個(gè)好主意。如果你的 ViewModel 擁有大量的代碼,承擔(dān)了過(guò)多的責(zé)任,那么:

  • 移除一部分邏輯到和 ViewModel 具有同樣作用域的地方。這部分將和應(yīng)用的其他部分進(jìn)行通信并更新
    ViewModel 持有的 LiveData。

  • 采用 Clean Architecture,添加一個(gè) domain 層。這是一個(gè)可測(cè)試,易維護(hù)的架構(gòu)。Architecture Blueprints 中有 Clean Architecture 的示例。

? 分發(fā)責(zé)任,如果需要的話,添加 domain 層

使用數(shù)據(jù)倉(cāng)庫(kù)

應(yīng)用架構(gòu)指南 中所說(shuō),大部分 App 有多個(gè)數(shù)據(jù)源:

  1. 遠(yuǎn)程:網(wǎng)絡(luò)或者云端

  2. 本地:數(shù)據(jù)庫(kù)或者文件

  3. 內(nèi)存緩存

在你的應(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ù)倉(cāng)庫(kù)作為你的數(shù)據(jù)的單一入口點(diǎn)。

處理數(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ò)誤和空集合?

  • 你可以通過(guò) ViewModel 暴露出一個(gè) LiveData<MyDataState>,MyDataState 可以包含數(shù)據(jù)正在加載,已經(jīng)加載完成,發(fā)生錯(cuò)誤等信息。

  • 你可以將數(shù)據(jù)包裝在具有狀態(tài)和其他元數(shù)據(jù)(如錯(cuò)誤消息)的類(lèi)中。查看示例中的 Resource 類(lèi)。

? 使用包裝類(lèi)或者另一個(gè) LiveData 來(lái)暴露數(shù)據(jù)的狀態(tài)信息

保存 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),使用 onSaveInstanceState() 和 ViewModel 組合。

詳見(jiàn):ViewModels: Persistence, onSaveInstanceState(), Restoring UI
State and Loaders
。

Event

Event 指只發(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)的一部分。

把事件設(shè)計(jì)成狀態(tài)的一部分。更多細(xì)節(jié)請(qǐng)閱讀 LiveData with SnackBar,Navigation and other events (the SingleLiveEvent case)

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)。

Observer pattern in the UI and callbacks in the data layer

當(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)存泄露。

The activity is nished but the ViewModel is still around

如果 ViewModel 很輕量,或者保證操作很快就會(huì)結(jié)束,這種泄露也不是什么大問(wèn)題。但是,事實(shí)并不總是這樣。理想情況下,只要沒(méi)有被 View 觀察了,ViewModel 就應(yīng)該被釋放。

你可以選擇下面幾種方式來(lái)達(dá)成目的:

  • 通過(guò) ViewModel.onCLeared() 通知數(shù)據(jù)倉(cāng)庫(kù)釋放 ViewModel 的回調(diào)

  • 在數(shù)據(jù)倉(cāng)庫(kù) Repository 中使用 弱引用 ,或者 Event Bu(兩者都容易被誤用,甚至被認(rèn)為是有害的)。

  • 通過(guò)在 View 和 ViewModel 中使用 LiveData 的方式,在數(shù)據(jù)倉(cāng)庫(kù)和 ViewModel 之間進(jìn)程通信

? 考慮邊界情況,內(nèi)存泄露和耗時(shí)任務(wù)會(huì)如何影響架構(gòu)中的實(shí)例。

? 不要在 ViewModel 中進(jìn)行保存狀態(tài)或者數(shù)據(jù)相關(guān)的核心邏輯。 ViewModel 中的每一次調(diào)用都可能是最后一次操作。

數(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)題。Transformations.switchMap 可以讓你根據(jù)一個(gè) LiveData 實(shí)例的變化創(chuàng)建新的 LiveData。它還允許你通過(guò)調(diào)用鏈傳遞觀察者的生命周期信息:

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 觀察了 repo,那么同樣的 LifecycleOwner 將被應(yīng)用在 repository.loadRepo(repoId) 的調(diào)用上。

無(wú)論什么時(shí)候你在 ViewModel 內(nèi)部需要一個(gè) LifeCycle 對(duì)象時(shí),Transformation 都是一個(gè)好方案。

繼承 LiveData

在 ViewModel 中使用 LiveData 最常用的就是 MutableLiveData,并且將其作為 LiveData 暴露給外部,以保證對(duì)觀察者不可變。

如果你需要更多功能,繼承 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ò) onActive() 來(lái)開(kāi)啟服務(wù)加載數(shù)據(jù)。但是除非你有一個(gè)很好的理由來(lái)說(shuō)明你不需要等待 LiveData 被觀察。下面這些通用的設(shè)計(jì)模式:

你并不需要經(jīng)常繼承 LiveData 。讓 Activity 和 Fragment 告訴 ViewModel 什么時(shí)候開(kāi)始加載數(shù)據(jù)。

分割線

翻譯就到這里了,其實(shí)這篇文章已經(jīng)在我的收藏夾里躺了很久了。
最近 Google 重寫(xiě)了 Plaid 應(yīng)用,用上了一系列最新技術(shù)棧, AAC,MVVM, Kotlin,協(xié)程 等等。這也是我很喜歡的一套技術(shù)棧,之前基于此開(kāi)源了 Wanandroid 應(yīng)用 ,詳見(jiàn) 真香!Kotlin+MVVM+LiveData+協(xié)程 打造 Wanandroid!

當(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 !

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多

    国产伦精品一区二区三区精品视频| 日韩av生活片一区二区三区| 国产精品成人又粗又长又爽| 国产一区二区精品丝袜| 国产精品午夜福利在线观看| 欧美日韩亚洲国产精品| 欧美亚洲国产日韩一区二区| 成年人黄片大全在线观看| 中国少妇精品偷拍视频| 国产毛片av一区二区三区小说| 99久久精品午夜一区二| 国产99久久精品果冻传媒| 高跟丝袜av在线一区二区三区| 经典欧美熟女激情综合网| 亚洲乱妇熟女爽的高潮片| 中文字幕日韩无套内射| 91精品国产品国语在线不卡| 国产av天堂一区二区三区粉嫩| 国产肥女老熟女激情视频一区| 国产精品熟女乱色一区二区| 99亚洲综合精品成人网色播| 欧美人妻免费一区二区三区| 亚洲专区中文字幕视频| 精品视频一区二区不卡| 国产又黄又爽又粗视频在线| 国产午夜福利在线免费观看| 夫妻性生活动态图视频| 亚洲日本韩国一区二区三区| 国产一区二区三区色噜噜| 国产超碰在线观看免费| 国产成人综合亚洲欧美日韩| 欧美亚洲91在线视频| 欧美日韩精品视频在线| 亚洲人妻av中文字幕| 青青操精品视频在线观看| 青青免费操手机在线视频| 精品欧美一区二区三久久| 在线观看日韩欧美综合黄片| 欧美日韩黑人免费观看| 高中女厕偷拍一区二区三区| 欧美av人人妻av人人爽蜜桃|