Android Activity 重建之狀態保存與恢復
為什么要重建 Activity ?
Activity 負責用戶界面,提供視圖顯示和接收用戶輸入。用戶界面的顯示和交互依賴于系統的配置,當系統的配置發生了變化,就需要修改用戶界面。Android 默認通過重建 Activity,用同類型的新的 Activity 對象替換舊的 Activity 對象的方式來將系統配置的最新狀態應用到用戶界面上。
會引發系統重建 Activity 的系統配置有很多,比如屏幕方向、系統語言、字體等。當這些配置發生變化,系統就進入重建 Activity 的流程。
除了系統配置的變更,系統回收后臺進程導致 Activity 銷毀,當用戶重新回到后臺的應用的時候,也會觸發重建流程。
要注意,這個銷毀的過程是靜默的、“未經用戶允許的”,是系統自主行為。嚴格來說,Android 應該只在內存不足時回收后臺進程,但是實際上生產裝載 Android 系統的廠商有著自己的考量。比如長時間在后臺運行的 App ,幾小時甚至幾天都沒有再打開它,他們就會考慮讓系統回收其進程;又或者用戶希望系統的運行地省電,那么后臺的程序則會更容易被回收。
相比之下,配置變更引起的重建是一個確定的過程,配置變更就立刻重建;而回收后臺進程之后的重建則取決于用戶是否重新回到后臺應用,以及設備廠商想不想讓系統重建。如果他們不想,就會修改 Android 的源代碼,執行他們自己的策略。
狀態是什么?
狀態指的是 Acticity 內會可能會變化的細節,比如界面上顯示的文本、圖片,用戶在輸入框輸入的文本、勾選的選擇框,用戶添加或刪除的數據,也包括無需用戶參與,程序自主發生的改變。
當 Activity 發生了變化,而之后系統又重建了 Activity,新的替換舊的,一切回到我們初始化 Activity 時的狀態,所有的改變都會丟失。
我們想要將舊的 Activity 發生的變化應用到新的 Activity 上,就需要保存一個數據來承載發生的變化并在之后使用該數據的方法。Android 的工程師也想到了這些,所以提供了這樣的功能。
如何保存與恢復?
onSaveInstanceState(Bundle) 和 onRetainNonConfigurationInstance() 兩個方法就是用來保存 Activity 狀態的,還有兩個與前兩者一一對應的 onRestoreInstanceState(Bundle) 與 getLastNonConfigurationInstance() 方法是獲取狀態的。
既然準了備兩個組合,其功能想來是有差異的,雖然都有在重建 Activity 時保存與傳遞數據的功能,但是它們應對不同的場景。
一個是應對 Activity 進程被回收后在新的進程重建 Activity 的場景,另一個是應對系統狀態變化后在同一進程重建 Activity 的場景。但是它們功能又有重合的地方,前者同時也具備后者的功能,但是我們應該讓它們各司其職。
onSaveInstanceState() 和 onRestoreInstanceState() 是如何工作的?
在 Activity 的重建過程中,舊的 Activity 對象將被銷毀之際,也即在調用其生命周期方法 onStop()之后 與 onDestory() 之前,系統調用其 onSaveInstanceState() 方法,將 Bundle 類型的數據容器傳遞進去,該 Activity 將變化的狀態存放到該容器,隨后該 Activity 被銷毀,該過程即“保存狀態”;
只要 Activity 是“被動銷毀”或“有被動銷毀的可能”,系統就一定會保存狀態,以便之后重建 Activity 時恢復狀態,它們分別對應著“系統配置的變化”與“系統回收后臺進程”對 Activity 的影響。回到啟動器桌面、跳轉到當前應用或其他應用的 Activity,都會使當前 Activity 進入后臺,有被回收的可能,從而觸發該方法的調用;而用戶主動終止 Activity 進程導致 Activity 被銷毀,明確不會重建 Activity,系統則不會調用此方法。
之后在合適的時機,系統重建 Activity,新的 Activity 對象被創建,系統
會調用其 onRestoreInstanceState() 方法,其調用時機是在 onStart() 與 onResume() 之間,將 Bundle 類型的數據容器傳遞進去,該 Activity 從中取出舊的 Activity 狀態應用于自身,該過程即“恢復狀態”。
onRestoreInstanceState() 只在 Activity 被重建時才會調用,和 onSaveInstanceState() 是成對地被調用。而當用戶主動結束 Activity 進程,則不會重建 Activity,自然也無法調用。又或者 Activity 是正常新建的,此方法也不執行。
這一組方法是為跨進程邊界的數據傳遞所準備的功能,這兩者都接收一個 Bundle 類型的參數,它是一個存放數據的容器。因為進程間的內存空間是獨立的,不能直接傳遞數據引用,故而所有存放到該容器的數據都必須是可被序列化的,以便在進程間傳遞數據。
另外,即使系統配置更改所觸發的 Activity 重建過程所涉及的兩個 Activities 對象在同一進程,系統也會調用這兩個方法,但數據的傳遞方式不是引用傳值,Bundle 還是被通過序列化的方式傳遞到主線程,再由主線程傳遞回來,再經歷反序列化,轉換為 Bundle 對象。
需要格外注意,使用 Bundle 傳遞數據有大小數據限制,上限為 1Mb,它是為簡化進程間通信進行設計的,不是為了拷貝大量數據。這意味著我們需要分析 Activity 的狀態,僅傳遞恢復 Activity 狀態的必要數據,而不是包含所有狀態的數據,非必要數據可以通過文件存儲或數據庫的方式傳遞。
比如說視圖展示一個列表,有很多項,其數據由一個 List 對象維護,用戶當前滾動到了列表視圖的特定位置。
此時若應用進入后臺,并被回收,我們不能將列表數據和定位信息都保存起來。系統回收 Activity 進程的目的往往是為了釋放內存,我們卻留存一份可能會占用比較大內存的數據,就違背了系統的意圖,還很有可能超出傳遞數據的上限。
恢復視圖的必要數據是列表的定位,我們僅保存并傳遞這個定位即可。至于列表的數據,保存到數據庫或者序列化后保存到文件中。當調用新的 Activity 的 onRestoreInstanceState() 方法時,我們讀取到了定位數據,然后在從文件中讀取緩存的序列化數據,反序列化后渲染到列表視圖,再根據定位信息恢復列表視圖的位置。
盡量不要在 onCreate(Bundle) 生命周期方法中恢復數據
系統在重建 Activity 過程中,在調用 onCreate() 時,也會傳遞 Bundle 數據容器,此時 Bundle 有值,是先前“保存狀態”時定義的;但是如果 Activity 不是重建的,而是正常新建的,那這個值就是 null,如果不經驗證就使用,程序會崩潰。
onRetainNonConfigurationInstance() 和 getLastNonConfigurationInstance() 如何工作?
它們是為同一進程中的 Activity 重建時傳遞數據所準備的功能,可以傳遞任何數據類型,是引用傳值。
系統調用舊的 Activity 的 onRetainNonConfigurationInstance()方法,該方法返回一個任意數據類型,系統保存它;新的 Activity 調用 getLastNonConfigurationInstance() 獲取保存的數據,并做類型轉換以取用數據。
onRetainNonConfigurationInstance() 的調用事件在 onSaveInstanceState() 之后,在 onDestory() 之前; getLastNonConfigurationInstance() 在 Activity 的任何生命周期方法中都能訪問。
通過這種方式,我們可以傳遞承載 Activity 所有的狀態的數據,并將該數據應用到重建的 Activity 中。
這一組方法永遠是 Activity 在前臺時被調用,與前面所提到的那組方法唯一重疊的地方就是在受到系統參數變化的影響時,系統會調用 onSaveInstanceState() 與 onRetainNonConfigurationInstance() 兩個方法來“保存狀態”,但是 另外的兩個恢復狀態的方法,只有 onRestoreInstanceState() 由系統調用,而 getLastNonConfigurationInstance() 的調用時機掌握在我們手里。
注意 Manifest 聲明文件中聲明 activity 的 android:configChanges 屬性對 Activity 重建過程的影響
configChanges 對應著系統屬性的變化,聲明該屬性意味著允許當前 Activity 響應指定的系統屬性變化,而不是在這些屬性變化時重建 Activity。該屬性允許多個值,使用 | 分隔。
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name">
上面的生命告知系統,該 Activity 希望響應屏幕方向和屏幕大小的變化,這會使系統在屬性變化時調用 Activity 的 onConfigurationChanged() 方法,隨后 Activity 調用關聯視圖的 onConfigurationChanged() 方法。
這些方法接收 Configuration 類型的對象,它承載著所有代表系統最新配置的數據,使用這些數據對系統屬性變更作出響應。
怎么組合使用上面的兩組方法?
使用 onSaveInstanceState() 和 onRestoreInstanceState() 恢復因系統回收 Activity 進程導致重建的 Activity 的狀態。
使用 onRetainNonConfigurationInstance() 和 getLastNonConfigurationInstance() 恢復因系統狀態變化后在同一進程重建的 Activity 的狀態。
維護一個 isRestored 字段表示是否已恢復 Activity 狀態。
在 onCreate() 方法中調用 getLastNonConfigurationInstance() 取得數據,如果取得了數據,說明 Activity 一定是在當前進程中重建的,然后為 Activity 恢復狀態,設置為 isRestored 為 true;如果數據為空說明是在新進程中重建的,什么也不做。
在 onRestoreInstanceState() 中判斷 isRestored,若為 true,說明 Activity 是因為系統配置變化被重建的,已經使用了 getLastNonConfigurationInstance() 的數據恢復了狀態;否則就是 Activity 進程被回收后在新進程重建的,需要在 onRestoreInstanceState() 方法中恢復。

浙公網安備 33010602011771號