Android liveData 和ViewModel 使用翻譯

簡(jiǎn)評(píng):MVVM 是谷歌提出的一種 Android 架構(gòu)模式,結(jié)合了 Data Binding 和一些生命周期組件 LiveData 和 ViewModel 等。詳情可以查看谷歌官方樣例庫

View 和 ViewModels

理論情況下,ViewModel 不需要知道任何關(guān)于 Android 的東西。這提供了可測(cè)試性,防止內(nèi)存泄漏和模塊化的好處。一條基本規(guī)制是確保在你的 ViewModels 類中沒有任何 android.* 的類導(dǎo)入(android.arch.* 例外)。這點(diǎn)和 Presenter 一樣。

??別讓 ViewModels(和 Presenters)知道任何關(guān)于 Android 框架的類。

條件語句,循環(huán)和一般決策應(yīng)該在 app 的 ViewModels 或其他層次中完成,而不是在 Activity 或 Fragment 中。View 通常不是單元測(cè)試的(除非你使用?Robolectric),所以越少代碼越好。View 僅僅應(yīng)該知道如何展示數(shù)據(jù)以及發(fā)送用戶的事件給 ViewModel(或 Presenter)。這就叫被動(dòng)視圖模式。

??在 Activities 和 Fragments 中保持最小化的邏輯

在 ViewModels 中引用 View

ViewModels?和 Activities 或 Fragments 相比有著不同的范圍。當(dāng)一個(gè) ViewModel 存活并運(yùn)行時(shí),一個(gè) Activity 可以處于它生命周期的任何一個(gè)狀態(tài)。在 ViewModel 不知道的情況下,Activities 和 Fragments 可以被再次摧毀和創(chuàng)建。

ViewModels 在配置改變時(shí)仍然存在

將 View 的引用(Activity 或 Fragment)傳遞到 ViewModel 是一個(gè)嚴(yán)重的風(fēng)險(xiǎn)。假設(shè) ViewModel 中請(qǐng)求了網(wǎng)絡(luò)數(shù)據(jù),數(shù)據(jù)在之后的一段時(shí)間返回。此時(shí),View 的引用有可能被回收或者舊的 Activity 不再可見,這樣就產(chǎn)生了內(nèi)存泄漏,甚至崩潰。

??避免在 ViewModels 中引用 Views

在 ViewModels 和 Views 中溝通推薦的方式是觀察者模式,利用 LiveData 或者其他庫提供的可觀察的方式。

觀察者模式

通過觀察者模式,在 Android 展示層中讓 View(Activity 或 Fragment)觀察(訂閱改變) ViewModel 顯得非常方便。因?yàn)?ViewModel 不需要知道 Android 內(nèi)容,它也不知道 Android 是如何頻繁地殺死 View 的。好處在于:

ViewModels 在配置改變時(shí)依然存在,因此當(dāng)旋轉(zhuǎn)屏幕時(shí)不需要再次查詢內(nèi)部數(shù)據(jù)(數(shù)據(jù)庫或網(wǎng)絡(luò))。

當(dāng)一個(gè)長(zhǎng)時(shí)間的操作結(jié)束時(shí),在 ViewModel 中可觀察的部分更新了。無論數(shù)據(jù)是否被觀察了,當(dāng)嘗試更新不存在的 View 時(shí)沒有空指針異常的發(fā)生。

ViewModels 不引用 View,所以減少了內(nèi)存泄漏的風(fēng)險(xiǎn)。

private void subscribeToModel() {

? // Observe product data

? viewModel.getObservableProduct().observe(this, new Observer() {

? ? ? @Override

? ? ? public void onChanged(@Nullable Product product) {

? ? ? ? mTitle.setText(product.title);

? ? ? }

? });

}

??在 UI 中注冊(cè)數(shù)據(jù)推送,讓 UI 觀察它的改變。

臃腫的 ViewModels

如果你的 ViewModel 中放了太多代碼或者太多的職責(zé),可以考慮:

將一些邏輯移動(dòng)到 presenter,它和 ViewModel 有相同的范圍。它可以和你 app 中的其他部分溝通并更新 ViewModel 中 LiveData 的持有者。

添加一個(gè)領(lǐng)域?qū)硬⒉扇?a target="_blank" rel="nofollow">簡(jiǎn)潔的架構(gòu),這樣有利于測(cè)試和維護(hù),也同樣促進(jìn)了快速脫離主線程。在?架構(gòu)藍(lán)圖中有一個(gè)簡(jiǎn)潔架構(gòu)的樣例。

??必要時(shí)分散職責(zé),增加領(lǐng)域?qū)印?/b>

使用數(shù)據(jù)倉庫

在 App 架構(gòu)指南中,可以看到大多數(shù)應(yīng)用都有多種數(shù)據(jù)來源,比如:

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

本地:數(shù)據(jù)庫或文件

內(nèi)存緩存

在你的應(yīng)用中擁有數(shù)據(jù)層是一個(gè)好主意,展示層完全注意不到。保留緩存、同步數(shù)據(jù)庫和網(wǎng)絡(luò)的算法都是無關(guān)緊要的。擁有一個(gè)完全隔離的倉庫類作為一個(gè)單一的入口來處理這些復(fù)雜的事情是非常推薦的。

如果你有多種并且不同的數(shù)據(jù)模型,考慮添加多種倉庫。

??添加一個(gè)數(shù)據(jù)倉庫作為訪問數(shù)據(jù)的單一入口。

處理數(shù)據(jù)狀態(tài)

考慮這個(gè)場(chǎng)景:你正在觀察 ViewModel 中暴露的 LiveData,它包含一個(gè)展示項(xiàng)目的列表。那么在數(shù)據(jù)加載,網(wǎng)絡(luò)錯(cuò)誤或者空列表時(shí),視圖該如何呈現(xiàn)這些變化呢?

你可以在 ViewModel 中暴露一個(gè) LiveData。例如,MyDataState 應(yīng)該包含那些數(shù)據(jù)是否正在加載,或者加載成功或加載失敗的信息。

你可以把數(shù)據(jù)包裹在一個(gè)保存了狀態(tài)或其他元數(shù)據(jù)(例如一條錯(cuò)誤消息)的類中??纯次覀儤永械?Resource?類。

??使用一些包裹類或另一個(gè) LiveData 來暴露你數(shù)據(jù)的信息。

保存 Activity 狀態(tài)

Activity 狀態(tài)是一些當(dāng) Activity 消失時(shí)(意味著被回收或進(jìn)程被殺死)你需要重新恢復(fù)屏幕的信息。旋轉(zhuǎn)屏幕是一個(gè)最顯著的案例,還好我們有 ViewModel。狀態(tài)保存在 ViewModel 中是安全的。

然而在某些場(chǎng)景中,當(dāng) ViewModel 也消失時(shí),你可能也需要恢復(fù)狀態(tài)。比如當(dāng)操作系統(tǒng)的資源緊缺時(shí),可能會(huì)殺死你的進(jìn)程。

為了高效地保存和恢復(fù) UI 狀態(tài),需要結(jié)合持續(xù)性,onSaveInstanceState() 以及 ViewModels。

看看例子:ViewModels:持續(xù),onSaveInstanceState(),恢復(fù) UI 狀態(tài)和裝載器

事件

一個(gè)事件指發(fā)送一次的動(dòng)作。ViewModels 暴露了數(shù)據(jù),但什么是事件呢?例如,導(dǎo)航事件或者展示 Snackbar 消息都是應(yīng)該只執(zhí)行一次的動(dòng)作。

事件的概念并不能很好的展示 LiveData 是如何存儲(chǔ)和恢復(fù)數(shù)據(jù)的。來看一下下面的 ViewModel:

LiveData snackbarMessage = new MutableLiveData<>();

一個(gè) Activity 開始觀察這個(gè)數(shù)據(jù),并且 ViewModel 完成了一個(gè)操作之后它需要更新這條消息:

snackbarMessage.setValue("Item saved!");

Activity 收到這條消息,并展示在 Snackbar 中。這顯然沒毛病。

然而,如果用戶旋轉(zhuǎn)手機(jī),創(chuàng)建了新的 Activity 并開始觀察。當(dāng) LiveData 觀察發(fā)生后,Activity 立即收到了舊的值,這時(shí)消息再次展示了!

我們擴(kuò)展了 LiveData,并創(chuàng)建了一個(gè)類叫?SingleLiveEvent,作為剛剛問題的解決方案。它僅僅發(fā)送訂閱之后出現(xiàn)的更新。注意它只支持一個(gè)觀察者。

??為像導(dǎo)航或 Snackbar 消息等事件使用可觀察的行為如?SingleLiveEvent。

泄露 ViewModels

反應(yīng)式范例在 Android 中工作得很好,因?yàn)樗试S在 UI 和應(yīng)用的其他層次建立方便的連接。LiveData 是這個(gè)結(jié)構(gòu)中的關(guān)鍵組件,因此通常情況下你的 Activities 和 Fragments 都會(huì)觀察一個(gè) LiveData 實(shí)例。

ViewModels 和其他組件是如何溝通的取決于你,但要注意泄露和邊界情況。下圖中展示層使用了觀察者模式,數(shù)據(jù)層使用了回調(diào):

如果用戶退出了應(yīng)用,View 將會(huì)消失,因此 ViewModel 不再被觀察。如果倉庫是一個(gè)單例,或者應(yīng)用范圍的,那么倉庫將不會(huì)回收直到進(jìn)程被殺死。這只會(huì)在操作系統(tǒng)需要資源或者用戶手動(dòng)殺死應(yīng)用時(shí)才會(huì)發(fā)生。如果倉庫保留了 ViewModel 中回調(diào)的引用,那么 ViewModel 就會(huì)暫時(shí)泄露。

如果 ViewModel 存活或者被分配的操作很快就完成了,那么這個(gè)泄露沒什么。然而,不是所有的時(shí)候都這樣。理想情況下,ViewModels 應(yīng)該在沒有任何 Views 觀察它們時(shí)回收:

你可以采取以下選項(xiàng)來達(dá)到這個(gè)目的:

使用?ViewModel.onCleared()?你可以告訴倉庫扔掉 ViewModel 的回調(diào)。

在倉庫中你可以使用弱引用或者?EventBus(這兩者都容易濫用甚至有害)

??考慮邊界情況,泄露以及長(zhǎng)時(shí)間的操作如何影響你架構(gòu)中的實(shí)例。

??不要把保存清除狀態(tài)或者相關(guān)的關(guān)鍵邏輯放在 ViewModel 中。任何你從 ViewModel 中的調(diào)用都可能是最后一次。

在倉庫中的 LiveData

為了避免 ViewModels 的泄露和回調(diào)地獄,倉庫可以這樣觀察:

當(dāng) ViewModel 被清除時(shí)或者當(dāng) View 的生命周期結(jié)束時(shí),訂閱也被清除了:

如果用這種方式的話有個(gè) catch:如果你不訪問 LifecycleOwner,那么你怎么從倉庫訂閱 ViewModel 呢?使用?Transformations?可以很方便地解決這個(gè)問題。Transformations.switchMap 讓你可以創(chuàng)建一個(gè)新的 LiveData,可以對(duì)其他 LiveData 實(shí)例做出反應(yīng)。它也同樣允許你通過鏈來攜帶觀察者的生命周期信息:

LiveData repo = Transformations.switchMap(repoIdLiveData, repoId -> {

? ? ? ? if (repoId.isEmpty()) {

? ? ? ? ? ? return AbsentLiveData.create();

? ? ? ? }

? ? ? ? return repository.loadRepo(repoId);

? ? }

);

在這個(gè)例子中,當(dāng)觸發(fā)更新時(shí),函數(shù)就會(huì)被調(diào)用并將結(jié)果向下分發(fā)。一個(gè) Activity 可以觀察?repo?并且隨著repository.loadRepo(id)?的調(diào)用相同的 LifecycleOwner 會(huì)被使用。

? 無論何時(shí),當(dāng)你考慮在?ViewModel?中使用?生命周期對(duì)象時(shí),Transformation?都可能是個(gè)解決方案。

繼承 LiveData

LiveData 最普遍的用例就是在 ViewModels 中使用MutableLiveData?并且將它們作為?LiveData?暴露出去,使得它們?cè)谄渌^察者中不可改變。

如果你需要更多功能,繼承 LiveData 可以讓你知道何時(shí)觀察者正在活動(dòng)。當(dāng)你想要開始監(jiān)聽定位或者傳感器服務(wù)時(shí)這將會(huì)很有用。例如:

public class MyLiveData extends LiveData {

? ? public MyLiveData(Context context) {

? ? ? ? // Initialize service

? ? }

? ? @Override

? ? protected void onActive() {

? ? ? ? // Start listening

? ? }

? ? @Override

? ? protected void onInactive() {

? ? ? ? // Stop listening

? ? }

}

何時(shí)不繼承 LiveData

你也可以使用?onActive()?開始一些加載數(shù)據(jù)的服務(wù),但除非你有一個(gè)很好的理由,你沒有必要等待 LiveData 被觀察。一些普遍的模式:

在 ViewModel 中添加一個(gè)?start()?方法,并盡快調(diào)用它。[查看藍(lán)圖例子]

設(shè)置一個(gè)啟動(dòng)負(fù)載的屬性[查看?GithubBrowserExample]

??通常你不用繼承 LiveData。讓你的 Activity 或者 Fragment 來告訴 ViewModel 什么時(shí)候開始加載數(shù)據(jù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,134評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,653評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,738評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,179評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評(píng)論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,610評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,916評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容