ViewModel:持久化、onSaveInstanceState()、UI 狀態恢復和 Loader

介紹

我在上篇博文中用新的 ViewModel 類開發了一個簡單的用例來保存配置更改過程當中的籃球分數。ViewModel 被設計用來以與生命週期相關的方式保存和管理 UI 相關的數據。ViewModel 容許數據在例如屏幕旋轉這樣的配置更改後依然保留。html

如今,你可能會有幾個問題是關於 ViewModel 到底能作什麼。本文我將解答:前端

  • ViewModel 是否對數據進行了持久化? 簡而言之,沒有,還像日常那樣去持久化。
  • ViewModel 是 onSaveInstanceState 的替代品嗎? 簡而言之,不是,可是他們不無關聯,請繼續讀。
  • 我如何高效地使用 ViewModel 來保存和恢復 UI 狀態? 簡而言之,你能夠混合混合 ViewModels、 onSaveInstanceState()、本地持久化一塊兒使用。
  • ViewModel 是 Loader 的一個替代品嗎? 簡而言之,對,ViewModel 結合其餘幾個類能夠代替 Loader 使用。

圖模型是否對數據進行了持久化?

簡而言之,沒有。 還像日常那樣去持久化。react

ViewModel 持有 UI 中的臨時數據,可是他們不會進行持久化。一旦相關聯的 UI 控制器(fragment/activity)被銷燬或者進程中止了,ViewModel 和全部被包含的數據都將被垃圾回收機制標記。android

那些被多個應用共用的數據應該像正常那樣經過 本地數據庫,Shared Preferences,和/或者雲存儲被持久化。若是你想讓用戶在應用運行在後臺三個小時候後再返回到與以前徹底相同的狀態,你也須要將數據持久化。這是由於一旦你的活動進入後臺,此時若是你的設備運行在低內存的狀況下,你的應用進程是能夠被終止的。下面是 activity 類文檔中的一個手冊表,它描述了在 activity 的哪一個生命週期狀態時你的應用是可被終止的:ios

Activity 生命週期文檔git

在此提醒,若是一個應用進程因爲資源限制而被終止的話,則不是正常終止而且沒有額外的生命週期回調。這意味着你不能依賴於 onDestroy 調用。在進程終止的時候你沒有機會持久化數據。所以若是你想最大可能的保持數據不丟失,你應該在用戶一進入(activity)的時候就進行持久化。也就是說即使你的應用在因爲資源限制而被終止或者設備電量用完了的時候數據也將會被保存下來。若是你容許在相似設備忽然關機的狀況下丟失數據,你能夠在 'onStop()'回調的時候將其保存,這個方法在 activity 一進入後臺的時候就會被調用。github

ViewModel 是 onSaveInstanceState 的替代品嗎?

簡而言之,不是, 可是他們不無關聯,請繼續讀。web

理解 onSaveInstanceState()Fragment.setRetainInstance(true) 兩者之間的不一樣有助於理解了解這種差別的微妙之處。數據庫

onSaveInstanceState(): 這個回調是爲了保存兩種狀況下的少許 UI 相關的數據:後端

  • 應用的進程在後臺的時候因爲內存限制而被終止。
  • 配置更改。

onSaveInstanceState() 是被系統在 activity stopped 但沒有 finished 時調用的,而不是在用戶顯式地關閉 activity 或者在其餘情形而致使 finish() 被調用的時候調用。

注意,不少 UI 數據會自動地被保存和恢復:

「該方法的默認實現保存了關於 activity 的視圖層次狀態的臨時信息,例如 EditText 控件中的文本或者 ListView 控件中的滾動條位置。」 — Saving and Restoring Instance State Documentation

這些也是很好的例子說明了 onSaveInstanceState() 方法中存儲的數據的類型。onSaveInstanceState() 不是被設計來存儲相似 bitmap 這樣的大的數據的。onSaveInstanceState() 方法被設計用來存儲那些小的與 UI 相關的而且序列化或者反序列化不復雜的數據。若是被序列化的對象是複雜的話,序列化會消耗大量的內存。因爲這一過程發生在主線程的配置更改期間,它須要快速處理纔不會丟幀和引發視覺上的卡頓。

Fragment.setRetainInstance(true)Handling Configuration Changes documentation 描述了在配置更改期間的一個用來存儲數據的進程使用了一個保留的 fragment。這聽起來沒有 onSaveInstanceState() 涵蓋了配置更改和進程關閉兩種狀況那麼有用。建立一個保留 fragment 的好處是這能夠保存相似 image 那樣的大型數據集或者網絡鏈接那樣的複雜對象。

ViewModel 只能在配置更改相關的銷燬的狀況下保留,而不能在被終止的進程中存留。 這使 ViewModel 成爲搭配 setRetainInstance(true)(實際上,ViewModel 在幕後使用了一個 fragment 並將 setRetainInstance 方法中的參數設置爲 true) 一塊使用的 fragment 的一種替代品。

ViewModel 的其餘好處

ViewModel 和 onSaveInstanceState() 在 UI 數據的存儲方法上有很大差異。onSaveInstanceState() 是生命週期的一個回調函數,而 ViewModel 從根本上改變了 UI 數據在你的應用中的管理方式。下面是使用了 ViewModel 後比 onSaveInstanceState() 以外的更多的一些好處:

  • ViewModel 鼓勵良好的架構設計。數據與 UI 代碼分離,這使代碼更加模塊化且簡化了測試。
  • onSaveInstanceState() 被設計用來存儲少許的臨時數據,而不是複雜的對象或者媒體數據列表。一個 ViewModel 能夠代理複雜數據的加載,一旦加載完成也能夠做爲臨時的存儲
  • onSaveInstanceState() 在配置更改期間和 activity 進入後臺時被調用;在這兩種狀況下,若是你的數據被保存在 ViewModel 中,實際上並不須要從新加載或者處理他們。

我如何高效地使用 ViewModel 來保存和恢復 UI 狀態?

簡而言之,你能夠混合使用 ViewModelonSaveInstanceState()本地持久化。繼續讀看看如何使用。

重要的是你的 activity 維持着用戶指望的狀態,即使是屏幕旋轉,系統關機或者用戶重啓。如我剛纔所說,不要用複雜對象阻塞 onSaveInstanceState 方法一樣也很重要。你也不想在你不須要的時候從新從數據庫加載數據。讓咱們看一個 activity 的例子,在這個 activity 中你能夠搜索你的音樂庫:

Activity 未搜索時及搜索後的狀態示例。

用戶離開一個 activity 有兩種經常使用的方式,用戶指望的也是兩種不一樣的結果:

  • 第一個是用戶是否完全關閉了 activity。若是用戶將一個 activity 從 recents screen 中滑出或者導航出去或退出一個 activity 就能夠完全關閉它。這兩種情形都假設用戶永久退出了這個 activity,若是從新進入那個 activity,他們所指望的是一個乾淨的頁面。對咱們的音樂應用來講,若是用戶徹底關閉了音樂搜索的 activity 而後從新打開它,音樂搜索框和搜索結果都將被清除。
  • 另外一方面,若是用戶旋轉手機或者 在activity 進入後臺而後回來,用戶但願搜索結果和他們想搜索的音樂仍存在,就像進入後臺前那樣。用戶有數種途徑可使 activity 進入後臺。他們能夠按 home 鍵或者經過應用的其餘地方導航(出去)。抑或在查看搜索結果的時候電話打了進來或收到通知。然而用戶最終但願的是當他們返回到那個 activity 的時候頁面狀態與離開前徹底同樣。

爲了實現這兩種情形下的行爲,用能夠將本地持久化、ViewModel 和 onSaveInstanceState() 一塊兒使用。每一種都會存儲 activity 中使用的不一樣數據:

  • 本地持久化是用於存儲當打開或關閉 activity 的時全部你不想丟失的數據。

    舉例: 包含了音頻文件和元數據的全部音樂對象的集合。

  • ViewModel 是用於存儲顯示相關 UI 控制器的所需的全部數據。

    舉例: 最近的搜索結果。

  • onSaveInstanceState 是用於存儲在 UI 控制器被系統終止又重建後能夠輕鬆地從新加載 activity 狀態時所需的少許數據。在本地存儲中持久化複雜對象,在 onSaveInstanceState() 中爲這些對象存儲惟一的 ID,而不是直接存儲複雜對象。 舉例: 最近的搜索查詢。

在音樂搜索的例子中,不一樣的事件應該被這樣處理:

用戶添加一首音樂的時候 — ViewModel 會迅速代理本地持久化這條數據。若是新添加的音樂須要在 UI 上顯示,你還應該更新 ViewModel 中的數據來反應音樂的添加。謹記切勿在主線程中向數據庫插入數據。

當用戶搜索音樂的時候 — 任何從數據庫爲 UI 控制器加載的複雜音樂數據應該立刻存入 ViewModel。你也應該將搜索查詢自己存入 ViewModel。

當這個 activity 處於後臺而且被系統終止的時候 — 一旦 activity 進入後臺 onSaveInstanceState() 就會被調用。你應將搜索查詢存入 onSaveInstanceState() 的 bundle 裏。這些少許數據易於保存。這一樣也是使 activity 恢復到當前狀態所需的全部數據。

當 activity 被建立的時候 — 可能出現三種不一樣的方式:

  • Activity 是第一次被建立:在這種狀況下,onSaveInstanceState()方法中的 bundle 裏是沒有數據的,ViewModel 也是空的。建立 ViewModel 時,你傳入一個空查詢,ViewModel 會意識到尚未數據能夠加載。這個 activity 以一種全新的狀態啓動起來。
  • Activity 在被系統終止後建立:activity 的 onSaveInstanceState() 的 bundle 中保存了查詢。Activity 會將這個查詢傳入 ViewModel。ViewModel發現緩存中沒有搜索結果,就會使用給定的搜索查詢代理加載搜索結果。
  • Activity 在配置更改後被建立:Activity 會將本次查詢保存在 onSaveInstanceState() 的 bundle 參數中而且 ViewModel 也會將搜索結果緩存起來。你經過 onSaveInstanceState() 的 bundle 將查詢傳入 ViewModel,這將決定它已加載了必須的數據從而須要從新查詢數據庫。

這是一個良好的保存和恢復 activity 狀態的方法。基於你的 activity 的實現,你可能根本不須要 onSaveInstanceState()。例如,有些 activity 在被用戶關閉後不會以一個全新的狀態打開。通常地,當我在 Android 手機上關閉而後從新打開 Chrome 時,返回到了關閉 Chrome 以前正在瀏覽的頁面。若是你的 activity 行爲如此,你能夠不使用 onSaveInstanceState() 而在本地持久化全部數據。一樣以音樂搜索爲例,那意味着在例如 Shared Preferences 中持久化最近的查詢。

此外,當你經過 intent 打開一個 activity,配置更改和系統恢復這個 activity 時 bundle 參數都會被傳進來。若是搜索查詢是經過 intent 的 extras 傳進來,那麼你就可使用 extras 中的 bundle 代替 onSaveInstanceState() 中的 bundle。

不過,在這兩種場景中,你仍須要一個 ViewModel 來避免因配置更改而從新從數據庫中加載數據致使的資源浪費。

ViewModel 是 Loader 的一個替代品嗎?

簡而言之,對,ViewModel 結合其餘幾個類能夠代替 Loader 使用。

Loader 是 UI 控制器用來加載數據的。此外,Loader 能夠在配置更改期間保留,好比說在加載的過程當中你旋轉了手機屏幕。這聽起來很耳熟吧!

Loader ,特別是 CursorLoader,的常見用法是觀察數據庫的內容並保持數據與 UI 同步。使用 CursorLoader 後,若是數據庫其中的一個值發生改變,Loader 就會自動觸發數據從新加載而且更新 UI。

ViewModel 與其餘架構組件 LiveDataRoom 一塊兒使用能夠替代 Loader。ViewModel 保證配置更改後數據不丟失。LiveData 保證 UI 與數據同步更新。Room 確保你的數據庫更新時,LiveData 被通知到。

因爲 Loader 在 UI 控制器中做爲回調被實現,所以 ViewModel 的一個額外優勢是將 UI 控制器與數據加載分離開來。這能夠減小類之間的強引用。

一些使用 ViewModels 、LiveData 爲加載數據的方法:

  • 這篇文章中,Ian Lake 概述瞭如何使用 ViewModel 和 LiveData 來代替 AsyncTaskLoader
  • 隨着代碼變得愈來愈複雜,你能夠考慮在一個單獨的類裏進行實際的數據加載。一個 ViewModel 類的目的是爲 UI 控制器持有數據。加載、持久化、管理數據這些複雜的方法超出了 ViewModel 傳統功能的範圍。Guide to Android App Architecture 建議建立一個倉庫類。

「倉庫模塊負責處理數據操做。他們爲應用的其餘部分提供了一套乾淨的 API。當數據更新時他們知道從哪裏獲取數據以及調用哪一個 API。你能夠把他們當作是不一樣數據源(持久模型、web service、緩存等)之間的協調員。」 — Guide to App Architecture

結論以及進一步學習

在本文中,我回答了幾個關於 ViewModel 類是什麼和不是什麼的問題。關鍵點是:

  • ViewModel 不是持久化的替代品 — 當數據改變時像日常那樣持久化他們。
  • ViewModel 不是 onSaveInstanceState() 的替代品,由於他們在與配置更改相關的銷燬時保存數據,而不能在系統殺死應用進程時保存。
  • onSaveInstanceState() 並不適用於那些須要長時間序列化/反序列化的數據。
  • 爲了高效的保存和恢復 UI 狀態,能夠混合使用 持久化、onSaveInstanceState() 和 ViewModel。複雜數據經過本地持久化保存而後用 onSaveInstanceState() 來保存那些複雜數據的惟一 ID。ViewModel 在數據加載後將他們保存在內存中。
  • 在這個場景下,ViewModel 在 activity 旋轉或者進入後臺時仍保留數據,而單純用 onSaveInstanceState() 並沒那麼容易實現。
  • 結合 ViewModel 和 LiveData 一塊兒使用能夠代替 Loader。你可使用 Room 來代替 CursorLoader 的功能。
  • 建立倉庫類來支持一個可伸縮的加載、緩存和同步數據的架構。

想要更多 ViewModel 相關的乾貨?請看:

架構組件是基於你反饋來建立的。若是你有關於 ViewModel 或者任何架構組件的問題,請查看咱們的反饋頁面。關於本系列的任何問題,敬請留言。

掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索