11-保存狀態

保存狀態

未編輯

在發生系統發起的 Activity 或應用銷燬後,需要及時保存和恢復 Activity 的界面狀態,這是用戶體驗的一個至關重要的部分。在這些情況下,用戶希望界面狀態保持不變,但是系統會銷燬 Activity 及其中存儲的任何狀態。

要使系統行爲符合用戶預期,可以把 ViewModel 對象、onSaveInstanceState() 方法和/或本地存儲空間結合起來使用,從而在發生此類應用和 Activity 實例轉換後保持界面狀態。在決定如何組合這些選項時,需要考慮界面數據的複雜程度、應用的用例以及檢索速度與內存用量的權衡。

無論採用哪種方法,都應確保應用滿足用戶對其界面狀態的預期,並提供流暢、簡潔的界面(消除將數據載入界面過程中的延遲時間,尤其是在發生像旋轉這樣頻繁的配置更改之後)。在大多數情況下,您應同時使用 ViewModel 和 onSaveInstanceState()。

本頁討論了用戶對界面狀態的預期,可用於保留狀態的選項,以及每種選項的權衡因素和侷限性。

用戶預期和系統行爲

根據用戶執行的操作,他們會希望系統清除或保留 Activity 狀態。在某些情況下,系統會自動執行用戶預期的操作。但有時,系統會執行與用戶預期相反的操作。

用戶發起的界面狀態解除

用戶希望當他們啓動 Activity 時,該 Activity 的暫時性界面狀態會保持不變,直到用戶完全關閉 Activity 爲止。用戶可以通過以下方式完全關閉 Activity:

  • 按返回按鈕
  • 從“概覽”(“最近使用的應用”)屏幕中滑動關閉 Activity
  • 從 Activity 向上導航
  • 從“設置”屏幕中終止應用
  • 完成某種“完成”Activity(由 Activity.finish() 提供支持)

在這些完全關閉的情況下,用戶會認爲他們已經永久離開 Activity,如果他們重新打開 Activity,會希望 Activity 以乾淨的狀態啓動。系統在這些關閉場景中的基礎行爲符合用戶預期,即 Activity 實例將連同其中存儲的任何狀態以及與該 Activity 關聯的任何已保存實例狀態記錄一起被銷燬並從內存中移除。

關於完全關閉的此規則有一些例外情況,例如用戶可能希望瀏覽器爲他們打開的是他們在使用返回按鈕退出瀏覽器之前查看的網頁。

系統發起的界面狀態解除

用戶希望 Activity 的界面狀態在發生配置更改(例如旋轉或切換到多窗口模式)後保持不變。但是,默認情況下,系統會在發生此類配置更改時銷燬 Activity,從而清除存儲在 Activity 實例中的任何界面狀態。要詳細瞭解設備配置,請參閱配置參考頁面。請注意,您可以替換針對配置更改的默認行爲,但不建議這樣做。如需瞭解詳情,請參閱自行處理配置更改

如果用戶暫時切換到其他應用,稍後再返回到您的應用,他們也會希望 Activity 的界面狀態保持不變。例如,用戶在您的搜索 Activity 中執行搜索,然後按主屏幕按鈕或接聽電話,當他們返回搜索 Activity 時,希望看到搜索關鍵字和結果仍在原處,並和之前完全一樣。

在這種情況下,您的應用會被置於後臺,系統會盡最大努力將您的應用進程留在內存中。但是,當用戶轉而去與其他應用進行互動時,系統可能會銷燬該應用進程。在這種情況下,Activity 實例連同其中存儲的任何狀態都會一起被銷燬。當用戶重新啓動應用時,Activity 會出乎意料地處於乾淨狀態。要詳細瞭解進程終止行爲,請參閱進程和應用生命週期

用於保留界面狀態的選項

當用戶對界面狀態的預期與默認系統行爲不符時,您必須保存並恢復用戶的界面狀態,以確保系統發起的銷燬對用戶完全透明。

按照以下幾個會影響用戶體驗的維度考量,用於保留界面狀態的每個選項都有所差異:

ViewModel 已保存實例狀態 持久性存儲空間
存儲位置 在內存中 已序列化到磁盤 在磁盤或網絡上
在配置更改後繼續存在
在系統發起的進程終止後繼續存在
在用戶完成 Activity 關閉/onFinish() 後繼續存在
數據限制 支持複雜對象,但是空間受可用內存的限制 僅適用於基元類型和簡單的小對象,例如字符串 僅受限於磁盤空間或從網絡資源檢索的成本/時間
讀取/寫入時間 快(僅限內存訪問) 慢(需要序列化/反序列化和磁盤訪問) 慢(需要磁盤訪問或網絡事務)

使用 ViewModel 處理配置更改

ViewModel 非常適合在用戶正活躍地使用應用時存儲和管理界面相關數據。它支持快速訪問界面數據,並且有助於避免在發生旋轉、窗口大小調整和其他常見的配置更改後從網絡或磁盤中重新獲取數據。要了解如何實現 ViewModel,請參閱 ViewModel 指南

ViewModel 將數據保留在內存中,這意味着成本要低於從磁盤或網絡檢索數據。ViewModel 與一個 Activity(或其他生命週期所有者)相關聯,在配置更改期間保留在內存中,系統會自動將 ViewModel 與發生配置更改後產生的新 Activity 實例相關聯。

當用戶退出您的 Activity 或 Fragment 時,或者在您調用 finish() 的情況下,系統會自動銷燬 ViewModel,這意味着狀態會被清除,正如用戶在這些場景中所預期的一樣。

與已保存實例狀態不同,ViewModel 在系統發起的進程終止過程中會被銷燬。因此,您應將 ViewModel 對象與 onSaveInstanceState()(或其他一些磁盤持久性功能)結合使用,並將標識符存儲在 savedInstanceState 中,以幫助視圖模型在系統終止後重新加載數據。

如果您已有用於在發生配置更改後存儲界面狀態的內存中解決方案,則可能不需要使用 ViewModel。

使用 onSaveInstanceState() 作爲後備方法來處理系統發起的進程終止

onSaveInstanceState() 回調會存儲一些數據,如果系統銷燬後又重新創建界面控制器(如 Activity 或 Fragment),則需要使用這些數據重新加載該控制器的狀態。要了解如何實現已保存實例狀態,請參閱 Activity 生命週期指南中的“保存和恢復 Activity 狀態”。

已保存實例狀態捆綁包在配置更改和進程終止後都會保留,但受限於存儲容量和速度,因爲 onSavedInstanceState() 會將數據序列化到磁盤。如果序列化的對象很複雜,序列化會佔用大量的內存。因爲此過程在配置更改期間發生在主線程上,所以如果耗時太長,序列化可能會導致丟幀和視覺卡頓。

請勿將 onSavedInstanceState() 用於存儲大量的數據(如位圖),也不要用於存儲需要冗長的序列化或反序列化操作的複雜數據結構,而是隻能用於存儲基本類型和簡單的小對象,例如字符串。因此,請使用 onSaveInstanceState() 存儲最少量的數據(例如 ID),如果其他持久性機制失效,需要使用這些數據來重新創建必要的數據以將界面恢復到之前的狀態。大多數應用都應實現 onSaveInstanceState() 來處理系統發起的進程終止。

根據應用的用例,您可能完全不需要使用 onSaveInstanceState()。例如,瀏覽器可能會將用戶帶回他們在退出瀏覽器之前正在查看的確切網頁。如果 Activity 表現出這種行爲,則您可以放棄使用 onSaveInstanceState(),改爲在本地保留所有內容。

此外,如果您從 intent 打開 Activity,則當配置發生更改以及系統恢復該 Activity 時,會將 extra 捆綁包傳送給該 Activity。在 Activity 啓動時,如果一段界面狀態數據(例如搜索查詢)作爲 intent extra 傳入,則您可以使用 extra 捆綁包而不是 onSaveInstanceState() 捆綁包。要詳細瞭解 intent extra,請參閱 Intent 過濾器和 Intent 過濾器

在上述任一情況下,您仍然可以使用 ViewModel 來避免因在配置更改期間從數據庫重新加載數據而浪費週期時間。

如果要保留的是簡單的輕量級界面數據,那麼您可以單獨使用 onSaveInstanceState() 來保留狀態數據。

注意:您現在可以通過 ViewModel 的已保存狀態模塊(目前爲 Alpha 版)在 ViewModel 對象中提供對已保存狀態的訪問途徑。已保存狀態可通過 SavedStateHandle 對象來訪問。您可以在 Android 生命週期感知型組件 Codelab 中查看其使用方式。

針對複雜或大型數據使用本地持久性存儲來處理進程終止

只要您的應用安裝在用戶的設備上,持續性本地存儲(例如數據庫或共享偏好設置)就會繼續存在(除非用戶清除應用的數據)。雖然此類本地存儲空間會在系統啓動的活動和應用進程終止後繼續存在,但由於必須從本地存儲空間讀取到內存,因此檢索成本高昂。這種持久性本地存儲通常已經屬於應用架構的一部分,用於存儲您打開和關閉 Activity 時不想丟失的所有數據。

ViewModel 和已保存實例狀態均不是長期存儲解決方案,因此不能替代本地存儲空間,例如數據庫。您只應該使用這些機制來暫時存儲瞬時界面狀態,對於其他應用數據,應使用持久性存儲空間。請參閱應用架構指南,詳細瞭解如何充分利用本地存儲空間長期保留您的應用模型數據(例如在重啓設備後)。

管理界面狀態:分而治之

您可以通過在各種類型的持久性機制之間劃分工作,高效地保存和恢復界面狀態。在大多數情況下,這些機制中的每一種都應存儲 Activity 中使用的不同類型的數據,具體取決於數據複雜度、訪問速度和生命週期的權衡:

  • 本地持久性存儲:存儲在您打開和關閉 Activity 時不希望丟失的所有數據。

    • 示例:歌曲對象的集合,其中可能包括音頻文件和元數據。
  • ViewModel

    :在內存中存儲顯示關聯界面控制器所需的所有數據。

    • 示例:最近搜索的歌曲對象和最近的搜索查詢。
  • onSaveInstanceState()
    

    :存儲當系統停止後又重新創建界面控制器時輕鬆重新加載 Activity 狀態所需的少量數據。這裏指的是將複雜的對象保留在本地存儲空間中,並將這些對象的唯一 ID 存儲在

    onSaveInstanceState()
    

    中,而不是存儲複雜的對象。

    • 示例:存儲最近的搜索查詢。

例如,假設有一個用於搜索歌曲庫的 Activity。應按如下方式處理不同的事件:

當用戶添加歌曲時,ViewModel 會立即委託在本地保留此數據。如果新添加的這首歌曲應顯示在界面中,則您還應更新 ViewModel 對象中的數據以表明該歌曲已添加。切記在主線程以外執行所有數據庫插入操作。

當用戶搜索歌曲時,針對界面控制器從數據庫加載的任何複雜歌曲數據都應立即存儲在 ViewModel 對象中。您還應將搜索查詢本身保存在 ViewModel 對象中。

當 Activity 進入後臺時,系統會調用 onSaveInstanceState()。您應將搜索查詢保存在 onSaveInstanceState() 捆綁包中。該少量數據很容易保存。這也是使 Activity 恢復到當前狀態所需的所有信息。

恢復複雜的狀態:重組碎片

當到了用戶該返回 Activity 的時候,重新創建 Activity 存在兩種可能情況:

  • 在系統停止 Activity 後重新創建該 Activity。該 Activity 將查詢保存在 onSaveInstanceState() 捆綁包中,並且應將查詢傳遞給 ViewModelViewModel 發現它沒有緩存搜索結果,並使用指定的搜索查詢委託加載搜索結果。
  • 在配置更改後創建 Activity。該 Activity 將查詢保存在 onSaveInstanceState() 捆綁包中,而且 ViewModel 已緩存搜索結果。您將查詢從 onSaveInstanceState() 捆綁包傳遞到 ViewModel,以此確定它已加載必要的數據,且無需要從數據庫重新查詢數據。

注意:最初創建 Activity 時,onSaveInstanceState() 捆綁包不包含任何數據,且 ViewModel 對象爲空。創建 ViewModel 對象時,您將傳遞空白查詢,以此告知 ViewModel 對象尚沒有要加載的數據。因此,Activity 以空狀態啓動。

其他資源

要詳細瞭解如何保存界面狀態,請參閱以下資源。

博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章