在系統(tǒng)發(fā)起的活動(dòng)或應(yīng)用程序銷毀過程中,及時(shí)維護(hù)和恢復(fù)活動(dòng)的UI狀態(tài)是用戶體驗(yàn)的關(guān)鍵部分。在這些情況下,用戶希望UI狀態(tài)保持不變,但是系統(tǒng)已經(jīng)銷毀了Activity和存儲(chǔ)在其中的任何狀態(tài)。
要消除用戶期望和系統(tǒng)行為之間的差距,可以使用ViewModel對(duì)象、onSaveInstanceState()方法和/或本地存儲(chǔ)的組合,以在應(yīng)用程序和活動(dòng)實(shí)例轉(zhuǎn)換之間持久化UI狀態(tài)。如何組合這些選項(xiàng)取決于UI數(shù)據(jù)的復(fù)雜性、應(yīng)用程序的用例,以及檢索和內(nèi)存使用速度的考慮。
無論采用哪種方法,都應(yīng)該確保應(yīng)用程序滿足用戶對(duì)其UI狀態(tài)的期望,并提供一個(gè)平滑的、流暢的UI(避免將數(shù)據(jù)加載到UI中,特別是在頻繁發(fā)生的配置更改之后,比如旋轉(zhuǎn))。在大多數(shù)情況下,您應(yīng)該同時(shí)使用ViewModel和onSaveInstanceState()。
本文討論用戶對(duì)UI狀態(tài)的期望、保存狀態(tài)的選項(xiàng)、權(quán)衡和限制。
用戶的期望和系統(tǒng)的行為
根據(jù)用戶所采取的操作,他們可以期望活動(dòng)狀態(tài)被清除,或者狀態(tài)被保留。在某些情況下,系統(tǒng)會(huì)自動(dòng)執(zhí)行用戶期望的操作。在其他情況下,系統(tǒng)的功能與用戶期望的相反。
用戶發(fā)起的UI狀態(tài)解除
用戶希望在啟動(dòng)活動(dòng)時(shí),該活動(dòng)的臨時(shí)UI狀態(tài)將保持不變,直到用戶完全取消活動(dòng)為止。用戶可以通過以下方式完全取消活動(dòng):
- 按后退按鈕
- 從近期任務(wù)列表界面上滑動(dòng)Activity。
- 從活動(dòng)中導(dǎo)航出去。
- 從設(shè)置界面殺死應(yīng)用程序。
- 完成某種“完成”活動(dòng)(由Activity.finish()支持)
在這些完全解雇案例中,用戶的假設(shè)是他們已經(jīng)永久地離開了這個(gè)Activity,如果他們重新打開Activity,他們希望Activity從一個(gè)全新的狀態(tài)開始。這些解雇場(chǎng)景的底層系統(tǒng)行為與用戶期望相匹配——Activity實(shí)例將被銷毀并從內(nèi)存中刪除,以及存儲(chǔ)在其中的任何狀態(tài)以及與該Activity相關(guān)聯(lián)的任何保存的實(shí)例狀態(tài)記錄。
關(guān)于完全解雇的這條規(guī)則有一些例外——例如,用戶可能希望瀏覽器將他們帶到他們正在查看的確切網(wǎng)頁,然后使用back按鈕退出瀏覽器。
系統(tǒng)發(fā)起的UI狀態(tài)解除
用戶期望Activity的UI狀態(tài)在整個(gè)配置更改中保持不變,例如旋轉(zhuǎn)或切換到多窗口模式。但是,默認(rèn)情況下,當(dāng)發(fā)生這樣的配置更改時(shí),系統(tǒng)會(huì)破壞Activity,清除存儲(chǔ)在活動(dòng)實(shí)例中的任何UI狀態(tài)。要了解有關(guān)設(shè)備配置的更多信息,請(qǐng)參Configuration reference page。注意,可以(盡管不建議)覆蓋配置更改的默認(rèn)行為。請(qǐng)參閱Handling the Configuration Change Yourself以獲得更多細(xì)節(jié)。
用戶也希望如果他們暫時(shí)切換到另一個(gè)應(yīng)用程序,然后再回到你的應(yīng)用程序時(shí)Activity的UI狀態(tài)保持不變。例如,用戶在搜索Activity中執(zhí)行搜索,然后按home鍵或接聽一個(gè)電話——當(dāng)他們返回到搜索Activity時(shí),他們希望找到搜索關(guān)鍵字,結(jié)果仍然和以前一樣。
在這個(gè)場(chǎng)景中,你的應(yīng)用程序被放置在后臺(tái),系統(tǒng)會(huì)盡力將你的應(yīng)用程序保存在內(nèi)存中。然而,當(dāng)用戶與其他應(yīng)用程序交互時(shí),系統(tǒng)可能會(huì)破壞應(yīng)用程序進(jìn)程。在這種情況下,Activity實(shí)例將被銷毀,以及存儲(chǔ)在其中的任何狀態(tài)。當(dāng)用戶重新啟動(dòng)該應(yīng)用程序時(shí),該Activity居然處于一個(gè)全新的狀態(tài)中。要了解關(guān)于過程死亡的更多信息,請(qǐng)參見Processes and Application Lifecycle。
保存UI狀態(tài)的選項(xiàng)
當(dāng)用戶對(duì)UI狀態(tài)的期望與默認(rèn)系統(tǒng)行為不匹配時(shí),必須保存并恢復(fù)用戶的UI狀態(tài),以確保系統(tǒng)啟動(dòng)的銷毀對(duì)用戶是透明的。
保存UI狀態(tài)的每個(gè)選項(xiàng)都在以下幾個(gè)方面有所不同,它們影響了用戶體驗(yàn):
這里本是一張表格 googel官方的內(nèi)容錯(cuò)亂了 待定填入。
使用ViewModel管理 configuration changes
ViewModel管理存儲(chǔ)和管理ui相關(guān)數(shù)據(jù)比較理想。它允許快速訪問UI數(shù)據(jù),并幫助您避免通過旋轉(zhuǎn)、窗口調(diào)整和其他常見的配置更改從網(wǎng)絡(luò)或磁盤重新獲取數(shù)據(jù)。要學(xué)習(xí)如何實(shí)現(xiàn)ViewModel,請(qǐng)參見ViewModel guide。
ViewModel保留了內(nèi)存中的數(shù)據(jù),這意味著從ViewModel中檢索數(shù)據(jù)比從磁盤或網(wǎng)絡(luò)中獲取數(shù)據(jù)要便宜得多。ViewModel與Activity(或其他生命周期所有者)相關(guān)聯(lián)——它在配置更改期間駐留在內(nèi)存中,系統(tǒng)會(huì)自動(dòng)將視圖模型與新Activity實(shí)例關(guān)聯(lián)起來,該Activity實(shí)例是由配置更改產(chǎn)生的。
當(dāng)用戶退出Activity或Fragment,或者調(diào)用finish()時(shí),ViewModel會(huì)自動(dòng)被系統(tǒng)破壞,這意味著在這些場(chǎng)景中,用戶期望的狀態(tài)將被清除。
與保存的實(shí)例狀態(tài)不同,視圖模型在系統(tǒng)啟動(dòng)的進(jìn)程死亡過程中被銷毀。這就是為什么您應(yīng)該將ViewModel對(duì)象與onSaveInstanceState()(或其他磁盤持久性)結(jié)合使用,在savedInstanceState中存儲(chǔ)標(biāo)識(shí)符,以幫助ViewModel在系統(tǒng)死后重新加載數(shù)據(jù)。
如果您已經(jīng)有了一個(gè)用于在配置更改中存儲(chǔ)UI狀態(tài)的內(nèi)存解決方案,那么您可能不需要使用ViewModel。
使用onSaveInstanceState()作為備份來處理系統(tǒng)啟動(dòng)的進(jìn)程死亡
onSaveInstanceState()回調(diào)存儲(chǔ)了重新加載UI控制器(Activity 或Fragment)的狀態(tài)所需的數(shù)據(jù),如果系統(tǒng)被銷毀了,然后重新創(chuàng)建該控制器。要了解如何實(shí)現(xiàn)保存的實(shí)例狀態(tài),請(qǐng)參閱Activity Lifecycle guide.中的保存和恢復(fù)Activity狀態(tài)。
保存的實(shí)例狀態(tài)包同時(shí)保存配置更改和進(jìn)程死亡,但是由于onSavedInstanceState()將數(shù)據(jù)序列化到磁盤,因此受到存儲(chǔ)和速度的限制。如果序列化的對(duì)象是復(fù)雜的,序列化可以消耗大量內(nèi)存。因?yàn)檫@個(gè)過程在配置更改期間發(fā)生在主線程上,如果時(shí)間過長,序列化會(huì)導(dǎo)致掉幀和視圖卡頓。
不要使用存儲(chǔ)onSavedInstanceState()來存儲(chǔ)大量數(shù)據(jù),比如位圖,或者需要長時(shí)間序列化或反序列化的復(fù)雜數(shù)據(jù)結(jié)構(gòu)。相反,只存儲(chǔ)原始類型和簡單的小對(duì)象,比如字符串。因此,使用onSaveInstanceState()來存儲(chǔ)必要的最小數(shù)據(jù)量(如ID),以重新創(chuàng)建必要的數(shù)據(jù),以便在其他持久性機(jī)制失敗時(shí),將UI恢復(fù)到以前的狀態(tài)。大多數(shù)應(yīng)用程序應(yīng)該實(shí)現(xiàn)onSaveInstanceState()來處理系統(tǒng)啟動(dòng)的進(jìn)程死亡。
根據(jù)您的應(yīng)用程序的使用情況,您可能根本不需要使用onSaveInstanceState()。例如,瀏覽器可能會(huì)將用戶帶回他們正在查看的網(wǎng)頁,然后才退出瀏覽器。如果您的活動(dòng)行為是這樣的,您可以使用onSaveInstanceState(),而不是將所有的東西都保存在本地。
另外,當(dāng)您從意圖中打開一個(gè)Activity時(shí),當(dāng)配置發(fā)生變化時(shí),當(dāng)系統(tǒng)恢復(fù)活動(dòng)時(shí),額外的附加組件將被交付到活動(dòng)中。如果某個(gè)UI狀態(tài)數(shù)據(jù)(如搜索查詢)在Activity啟動(dòng)時(shí)作為額外的意圖傳入,那么您可以使用額外的bundle而不是onSaveInstanceState() bundle。要了解更多關(guān)于意圖的附加內(nèi)容,請(qǐng)看Intent and Intent Filters。
在任何一種情況下,您都應(yīng)該使用ViewModel,以避免在配置更改期間從數(shù)據(jù)庫中重新加載數(shù)據(jù)。
在UI數(shù)據(jù)保持簡單和輕量級(jí)的情況下,您可以使用onSaveInstanceState()來保存狀態(tài)數(shù)據(jù)。
使用本地持久性來處理復(fù)雜或大型數(shù)據(jù)的過程死亡。
持久的本地存儲(chǔ),例如數(shù)據(jù)庫或共享首選項(xiàng),只要您的應(yīng)用程序安裝在用戶的設(shè)備上(除非用戶為您的應(yīng)用程序清除了數(shù)據(jù)),它就會(huì)繼續(xù)存在。雖然這樣的本地存儲(chǔ)在系統(tǒng)啟動(dòng)的活動(dòng)和應(yīng)用程序進(jìn)程死亡時(shí)仍然存在,但是檢索起來很昂貴,因?yàn)樗仨殢谋镜卮鎯?chǔ)讀入內(nèi)存。通常,這種持久的本地存儲(chǔ)可能已經(jīng)成為應(yīng)用程序體系結(jié)構(gòu)的一部分,用于存儲(chǔ)您不想丟失的所有數(shù)據(jù),如果您打開和關(guān)閉該活動(dòng)。
ViewModel和save實(shí)例狀態(tài)都不是長期存儲(chǔ)解決方案,因此不能替代本地存儲(chǔ),比如數(shù)據(jù)庫。相反,應(yīng)該使用這些機(jī)制臨時(shí)存儲(chǔ)臨時(shí)UI狀態(tài),并為其他應(yīng)用程序數(shù)據(jù)使用持久存儲(chǔ)。有關(guān)如何利用本地存儲(chǔ)來持久存儲(chǔ)應(yīng)用程序模型數(shù)據(jù)的詳細(xì)信息,請(qǐng)參閱應(yīng)用程序架構(gòu)指南(例如,在設(shè)備重新啟動(dòng)時(shí))。
UI狀態(tài)的管理:分而治之
通過將工作劃分到各種類型的持久性機(jī)制中,您可以有效地保存和恢復(fù)UI狀態(tài)。在大多數(shù)情況下,每種機(jī)制都應(yīng)該根據(jù)數(shù)據(jù)復(fù)雜性、訪問速度和生命周期的權(quán)衡,存儲(chǔ)活動(dòng)中使用的不同類型的數(shù)據(jù)。
- 本地持久性:如果打開并關(guān)閉活動(dòng),存儲(chǔ)所有您不想丟失的數(shù)據(jù)。
- 示例:一組歌曲對(duì)象,可以包括音頻文件和元數(shù)據(jù)。
- ViewModel:存儲(chǔ)在內(nèi)存中的所有數(shù)據(jù),以顯示相關(guān)的UI控制器。
- 示例:最新搜索的歌曲對(duì)象和最新的搜索查詢。
- onSaveInstanceState():如果系統(tǒng)停止并重新創(chuàng)建UI控制器,則存儲(chǔ)少量數(shù)據(jù),以便輕松地重新加載活動(dòng)狀態(tài)。這里不存儲(chǔ)復(fù)雜對(duì)象,而是在本地存儲(chǔ)中保存復(fù)雜對(duì)象,并在onSaveInstanceState()中存儲(chǔ)這些對(duì)象的惟一ID。
- 示例:存儲(chǔ)最近的搜索查詢。
作為一個(gè)例子,考慮一個(gè)允許你搜索你的歌曲庫的活動(dòng)。下面是如何處理不同的事件:
當(dāng)用戶添加歌曲時(shí),ViewModel會(huì)立即委托在本地持久化該數(shù)據(jù)。如果這個(gè)新添加的歌曲應(yīng)該顯示在UI中,那么您也應(yīng)該更新ViewModel對(duì)象中的數(shù)據(jù),以反映歌曲的添加。記住要從主線程中刪除所有的數(shù)據(jù)庫。
當(dāng)用戶搜索一首歌曲時(shí),你從數(shù)據(jù)庫中加載的UI控制器的復(fù)雜歌曲數(shù)據(jù)應(yīng)該立即存儲(chǔ)在ViewModel對(duì)象中。您還應(yīng)該將搜索查詢本身保存在ViewModel對(duì)象中。
當(dāng)活動(dòng)進(jìn)入后臺(tái)時(shí),系統(tǒng)調(diào)用onSaveInstanceState()。您應(yīng)該在onSaveInstanceState()包中保存搜索查詢。這少量的數(shù)據(jù)很容易保存。它也是將活動(dòng)恢復(fù)到當(dāng)前狀態(tài)所需的所有信息。
恢復(fù)復(fù)雜狀態(tài):重新組裝
當(dāng)用戶返回到活動(dòng)時(shí),有兩個(gè)可能的場(chǎng)景來重新創(chuàng)建活A(yù)ctivity:
- Activity在系統(tǒng)停止后重新創(chuàng)建。該Activity將查詢保存在onSaveInstanceState()包中,并將查詢傳遞給ViewModel。ViewModel看到它沒有搜索結(jié)果緩存,并使用給定的搜索查詢加載搜索結(jié)果。
- 該Activity是在配置更改之后創(chuàng)建的。該Activity將查詢保存在onSaveInstanceState()包中,并且ViewModel已經(jīng)緩存了搜索結(jié)果。您將查詢從onSaveInstanceState()包傳遞到ViewModel,它確定它已經(jīng)加載了必需的數(shù)據(jù),并且不需要重新查詢數(shù)據(jù)庫。
注意:當(dāng)一個(gè)Activity最初創(chuàng)建時(shí),onSaveInstanceState() bundle不包含任何數(shù)據(jù),ViewModel對(duì)象為空。當(dāng)您創(chuàng)建ViewModel對(duì)象時(shí),您傳遞一個(gè)空查詢,該查詢告訴ViewModel對(duì)象沒有加載數(shù)據(jù)。因此,活動(dòng)從空狀態(tài)開始。