Android liveData 和ViewModel 使用翻譯

簡評(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è)長時(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">簡潔的架構(gòu),這樣有利于測(cè)試和維護(hù)剖膳,也同樣促進(jìn)了快速脫離主線程魏颓。在?架構(gòu)藍(lán)圖中有一個(gè)簡潔架構(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è)場景:你正在觀察 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 中是安全的。

然而在某些場景中塔淤,當(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(這兩者都容易濫用甚至有害)

??考慮邊界情況,泄露以及長時(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜈亩,隨后出現(xiàn)的幾起案子懦窘,更是在濱河造成了極大的恐慌,老刑警劉巖勺拣,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奶赠,死亡現(xiàn)場離奇詭異,居然都是意外死亡药有,警方通過查閱死者的電腦和手機(jī)毅戈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門苹丸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苇经,你說我怎么就攤上這事赘理。” “怎么了扇单?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵商模,是天一觀的道長。 經(jīng)常有香客問我蜘澜,道長施流,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任鄙信,我火速辦了婚禮瞪醋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘装诡。我一直安慰自己银受,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布鸦采。 她就那樣靜靜地躺著宾巍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渔伯。 梳的紋絲不亂的頭發(fā)上顶霞,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音锣吼,去河邊找鬼确丢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吐限,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褂始,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼诸典,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了崎苗?” 一聲冷哼從身側(cè)響起狐粱,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胆数,沒想到半個(gè)月后肌蜻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡必尼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年蒋搜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了篡撵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豆挽,死狀恐怖育谬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帮哈,我是刑警寧澤膛檀,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站娘侍,受9級(jí)特大地震影響咖刃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜憾筏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一嚎杨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧踩叭,春花似錦磕潮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斤富,卻和暖如春膏潮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背满力。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工焕参, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人油额。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓叠纷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潦嘶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涩嚣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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