儲存 UI 狀態

本指南說明使用者對 UI 狀態的期望,以及可用選項 以便保留狀態

在活動發生後快速儲存及還原活動的 UI 狀態。 破壞系統的活動或應用程式 對使用者來說至關重要 無須專人管理使用者會期望 UI 狀態維持不變,但系統可能仍會 刪除活動及其儲存狀態。

如要消除使用者期望和系統行為之間的差距,請使用 合併使用:

最佳解決方案取決於 UI 資料的複雜程度,以及應用程式的使用情況 並在資料存取速度和記憶體用量之間取得平衡。

確認您的應用程式符合使用者的以及快速、反應靈敏的 存取 API避免將資料載入 UI 時發生延遲,尤其是在載入資料之後 像是旋轉、旋轉

使用者期望和系統行為

視採取的行動而定,使用者會期望自己達到的活動狀態 或要保留的狀態在某些情況下,系統會 自動執行使用者預期的操作若是其他情況,系統 與使用者期望的行為不同

使用者啟動的使用者介面狀態關閉

使用者預期在啟動活動時,使用者介面狀態會暫時顯示為 該活動會保持不變,直到使用者完全關閉活動為止。 使用者可以按照下列步驟完全關閉活動:

  • 從「總覽」(近期存取) 畫面滑動以離開活動。
  • 從「設定」畫面終止或強制退出應用程式。
  • 正在重新啟動裝置。
  • 完成某種「完成」的任務動作 (由 Activity.finish())。

在上述完全關閉的狀況下,使用者的假設是 永久離開活動,如果重新開啟活動 他們預期活動會從乾淨的狀態開始。基礎系統 這類關閉情境的行為符合使用者的期望, 活動例項會遭到刪除,並從記憶體中移除 狀態,以及任何與 Deployment 程式碼相關聯的已儲存例項狀態記錄 活動。

這項規則關於完全關閉,有些例外狀況,例如: 使用者可能會希望瀏覽器能將他們導向確切的網頁 便離開瀏覽器。

系統啟動的使用者介面狀態關閉

使用者會預期活動的 UI 狀態在整個 例如旋轉或切換至多視窗模式 不過,在這類設定下,系統預設會刪除活動 系統會清除儲存在活動例項中的任何 UI 狀態。目的地: 如要進一步瞭解裝置設定,請參閱 設定參考資料頁面。請注意,您可以這麼做 (但我們不建議這麼做) 覆寫設定變更的預設行為請參閱處理 變更設定,以獲得更多資訊。

使用者在遇到以下情況時,也預期活動的 UI 狀態將維持不變 暫時切換到其他應用程式,稍後再返回查看。適用對象 例如,使用者在搜尋活動中進行搜尋,然後按下 主畫面按鈕或接聽來電 - 他們返回搜尋活動時 他們會預期上網時能找到搜尋關鍵字和結果 。

在此情況下,您的應用程式會在背景執行,系統會盡可能以最佳方式執行 將應用程式處理程序保留在記憶體中然而,系統可能會刪除 應用程式處理程序。因此 系統會刪除該活動例項,以及當中儲存的所有狀態。 重新啟動應用程式後,活動會以非使用者預期的乾淨狀態開始。 如要進一步瞭解程序死亡,請參閱「程序和應用程式生命週期」。

保留使用者介面狀態的選項

使用者對 UI 狀態的期望與預設系統不符時 就必須儲存及還原使用者的 UI 狀態,確保 使用者可以看到系統啟動的刪除作業。

每個保留 UI 狀態的選項在以下維度上各有不同 影響使用者體驗:

ViewModel 已儲存例項狀態 永久儲存空間
儲存位置 在記憶體中 在記憶體中 在磁碟或網絡上
在設定變更後仍持續 可轉移
在系統啟動的程序死亡後仍持續
在使用者完全關閉活動後仍持續/onFinish()
資料限制 複雜的物件可以正常運作,但空間會受限於可用記憶體 僅適用於原始類型和簡易的小型物件,例如字串 僅受限於磁碟空間,或擷取網路資源的費用 / 時間
讀取/寫入時間 快 (只須存取記憶體) 慢 (須進行序列化/去序列化) 慢 (須存取磁碟或執行網路交易)

使用 ViewModel 處理設定變更

當使用者處於閒置狀態時,ViewModel 可以有效儲存及管理 UI 相關資料 經常使用應用程式可讓您快速存取 UI 資料,並協助您 避免在旋轉、調整視窗大小及 其他常見的設定變更如要瞭解 ViewModel 請參閱「ViewModel 指南」。

ViewModel 會將資料保存在記憶體中,因此擷取比 磁碟或網路內的資料ViewModel 已與活動建立關聯 (或其他生命週期擁有者) - 它會在設定期間保留在記憶體中 而系統會自動連結 ViewModel 與新的 設定變更所產生的活動例項。

當使用者登出後,ViewModel 會自動刪除 活動或片段,或是您呼叫 finish(),就表示狀態 是否充分滿足使用者的期望

與已儲存的例項狀態不同,ViewModels 會在系統啟動期間刪除 程序終止。如要在 ViewModel 則使用 SavedStateHandle API。如果資料是 與使用者介面相關,且不需要保留在 ViewModel 中;請使用 View 系統中的 onSaveInstanceState(),或在 Jetpack 中為 rememberSaveable 。如果資料是應用程式資料,建議您保留資料 複製到磁碟

如果您已經有用來儲存 UI 狀態的記憶體內解決方案 但您可能不需要使用 ViewModel。

使用已儲存例項狀態做為備份,以處理系統啟動的程序終止

View 系統中的 onSaveInstanceState() 回呼, rememberSaveable (在 Jetpack Compose 中) 和 SavedStateHandle ViewModel 會儲存重新載入 UI 控制器狀態所需的資料,例如 活動或片段 (如果系統刪除後重新建立) 控制器如要瞭解如何使用 onSaveInstanceState,請參閱「儲存及還原活動狀態」一文 活動生命週期指南

已儲存的執行個體狀態組合會保留在設定變更與 但由於不同的 API 版本 序列化資料。序列化作業可能會耗用大量記憶體 序列化很複雜因為這個程序是在主執行緒上進行 在設定變更期間,長時間執行序列化可能會遺失 影格和視覺延遲

請勿使用已儲存的例項狀態儲存大量資料,例如點陣圖 也不需要長時間序列化的複雜資料結構 以及去序列化。只儲存原始類型和簡單的小型物件 例如 String。因此,使用已儲存的例項狀態來儲存最低限度的 必要資料 (例如 ID),重新建立還原 UI 所需的資料 當其他持續性機制失敗時,可復原回先前的狀態。大多數 應用程式應實作此方法,以處理系統啟動的程序死亡。

視應用程式的用途而定,您可能不需要使用已儲存的執行個體 完全不必變更例如,瀏覽器可能會將使用者帶往 離開瀏覽器前正在瀏覽的網頁。如果您的活動是 這樣一來,您就可以使用已儲存的執行個體狀態來取代現有執行個體 所有本機物件

此外,從意圖開啟活動時,額外項目套件會是 容器都會在設定變更時傳送給活動 系統會還原活動如果有 UI 狀態資料 (例如搜尋) 則會在活動啟動時做為額外意圖傳入, 可以使用額外套件,而非儲存的例項狀態組合。學習 如要進一步瞭解意圖額外項目,請參閱「意圖和意圖篩選器」。

無論是哪一種情況,您都應使用 ViewModel,避免 在變更設定期間,浪費週期會重新載入資料庫中的資料。

如果保留 UI 資料既簡單簡易,則可使用 儲存的執行個體狀態 API 來保留您的狀態資料。

使用 SavedStateRegistry 開啟已儲存的狀態

Fragment 1.1.0 或其轉換依附元件 Activity 開始 1.0.0,UI 控制器,例如 ActivityFragment SavedStateRegistryOwner,並提供SavedStateRegistry 繫結至該控制器SavedStateRegistry 可讓元件連線至 為使用或提供 UI 控制器的儲存狀態。例如: ViewModel 的已儲存狀態模組會使用 SavedStateRegistry 建立 SavedStateHandle 並提供給 ViewModel 物件。您可以擷取 從 UI 控制器中呼叫 SavedStateRegistry getSavedStateRegistry()

影響已儲存狀態的元件必須實作 SavedStateRegistry.SavedStateProvider,用於定義單一方法 呼叫 saveState()saveState() 方法可讓元件 會傳回 Bundle,其中包含應從該元件儲存的任何狀態。 SavedStateRegistry 會在 UI 的儲存狀態階段呼叫這個方法 控制器的生命週期

Kotlin

class SearchManager : SavedStateRegistry.SavedStateProvider {
    companion object {
        private const val QUERY = "query"
    }

    private val query: String? = null

    ...

    override fun saveState(): Bundle {
        return bundleOf(QUERY to query)
    }
}

Java

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String QUERY = "query";
    private String query = null;
    ...

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }
}

如要註冊 SavedStateProvider,請在以下位置呼叫 registerSavedStateProvider()SavedStateRegistry,傳遞用來連結供應器資料的鍵,做為 以及供應器先前儲存的供應商資料 呼叫 consumeRestoredStateForKey(),從儲存的狀態中擷取 在 SavedStateRegistry 上,傳入與供應器項目相關聯的金鑰。 資料。

ActivityFragment 中,您可以在 ActivityFragment 中註冊 SavedStateProvider onCreate(),然後再呼叫 super.onCreate()。或者,您可以設定 SavedStateRegistryOwner 上的 LifecycleObserver,會導入 LifecycleOwner,並在SavedStateProvider 會發生 ON_CREATE 事件。只要使用 LifecycleObserver,您就可以將 從先前儲存的狀態的 SavedStateRegistryOwner 本身。

Kotlin

class SearchManager(registryOwner: SavedStateRegistryOwner) : SavedStateRegistry.SavedStateProvider {
    companion object {
        private const val PROVIDER = "search_manager"
        private const val QUERY = "query"
    }

    private val query: String? = null

    init {
        // Register a LifecycleObserver for when the Lifecycle hits ON_CREATE
        registryOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_CREATE) {
                val registry = registryOwner.savedStateRegistry

                // Register this object for future calls to saveState()
                registry.registerSavedStateProvider(PROVIDER, this)

                // Get the previously saved state and restore it
                val state = registry.consumeRestoredStateForKey(PROVIDER)

                // Apply the previously saved state
                query = state?.getString(QUERY)
            }
        }
    }

    override fun saveState(): Bundle {
        return bundleOf(QUERY to query)
    }

    ...
}

class SearchFragment : Fragment() {
    private var searchManager = SearchManager(this)
    ...
}

Java

class SearchManager implements SavedStateRegistry.SavedStateProvider {
    private static String PROVIDER = "search_manager";
    private static String QUERY = "query";
    private String query = null;

    public SearchManager(SavedStateRegistryOwner registryOwner) {
        registryOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
            if (event == Lifecycle.Event.ON_CREATE) {
                SavedStateRegistry registry = registryOwner.getSavedStateRegistry();

                // Register this object for future calls to saveState()
                registry.registerSavedStateProvider(PROVIDER, this);

                // Get the previously saved state and restore it
                Bundle state = registry.consumeRestoredStateForKey(PROVIDER);

                // Apply the previously saved state
                if (state != null) {
                    query = state.getString(QUERY);
                }
            }
        });
    }

    @NonNull
    @Override
    public Bundle saveState() {
        Bundle bundle = new Bundle();
        bundle.putString(QUERY, query);
        return bundle;
    }

    ...
}

class SearchFragment extends Fragment {
    private SearchManager searchManager = new SearchManager(this);
    ...
}

使用本機常駐性來處理複雜或大型資料的程序終止

永久性本機儲存空間 (例如資料庫或共用偏好設定) 仍會保留 只要應用程式已安裝在使用者的裝置上 (除非 使用者清除應用程式資料)。雖然本機儲存空間會保留 系統啟動活動和應用程式程序死亡時, 因為必須從本機儲存空間讀取記憶體經常 這個永久性的本機儲存空間可能已經是應用程式的一部分 這個架構可用於儲存不想遺失的所有資料 活動。

不論是 ViewModel 還是已儲存的例項狀態,都不是長期儲存解決方案 因此不會取代本機儲存空間 (例如資料庫)。相反地 這些機制應該只用來暫時儲存暫時的 UI 狀態, 將永久儲存空間用於其他應用程式資料請參閱「應用程式架構指南」 進一步瞭解如何運用本機儲存空間保存應用程式模型 資料。

管理使用者介面狀態:分割及管理

將工作分配給各個工作,就能有效率地儲存和還原 UI 狀態。 不同類型的持續性機制在大多數情況下,上述機制 應根據 在資料複雜性、存取速度和生命週期之間取得平衡:

  • 本機常駐性:儲存您不想遺失的所有應用程式資料, 開啟並關閉活動
    • 範例:歌曲物件集合,可能包含音訊檔案 和中繼資料
  • ViewModel:將顯示 畫面 UI 狀態
    • 範例:最近查詢和最近查詢的歌曲物件 搜尋查詢。
  • 已儲存的例項狀態:儲存重新載入所需的少量資料 系統停止並重建 UI 時的 UI 狀態。系統不會儲存 請將複雜的物件保留在本機儲存空間,並儲存 已儲存的執行個體狀態 API 中這些物件的專屬 ID。
    • 範例:儲存最近的搜尋查詢。

舉例來說,可以讓您搜尋 歌曲庫。各種事件的處理方式如下:

使用者新增歌曲時,ViewModel 會立即委派給成員 以儲存這些資料如果這個新加入的歌曲應該顯示在使用者介面中, 您也應更新 ViewModel 物件中的資料,反映新增的 歌曲。請記得將主執行緒插入所有資料庫。

當使用者搜尋歌曲時,您從 資料庫,因此應立即儲存在 ViewModel 物件中,做為 也就是螢幕 UI 狀態

當活動進入背景,且系統會呼叫儲存的 例項狀態 API 時,搜尋查詢應儲存在已儲存的例項狀態中, 以防程序重新建立由於載入時需要這項資訊 應用程式資料,並將搜尋查詢儲存在 ViewModel 中 SavedStateHandle。這些是載入資料及載入資料所需的所有資訊, 讓 UI 回到目前狀態

還原複雜狀態:重新組合元素

使用者恢復活動時,有兩種可能 重新建立活動的情境:

  • 系統停止活動後,要重新建立活動。 系統會將查詢儲存在已儲存的執行個體狀態套件中,而 UI 如果未使用 SavedStateHandle,則應將查詢傳送至 ViewModelViewModel 發現沒有快取任何搜尋結果,且委派代表 載入搜尋結果。
  • 活動會在設定變更後建立。自 ViewModel以來 執行個體尚未刪除,ViewModel 已快取所有資訊 且無需重新查詢資料庫。
,瞭解如何調查及移除這項存取權。

其他資源

如要進一步瞭解如何儲存 UI 狀態,請參閱下列資源。

網誌