處理設定變更

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

部分裝置設定可能會在應用程式執行期間變更。這類設定包括但不限於:

  • 應用程式顯示大小
  • 螢幕方向
  • 字型大小和粗細
  • 語言代碼
  • 深色模式與淺色模式
  • 鍵盤可用性

這些設定變更大多是由使用者互動而產生。舉例來說,旋轉或折疊裝置會變更應用程式可用的螢幕空間。同樣地,改變字型大小、語言或偏好的主題等裝置設定,也會變更 Configuration 物件中各自對應的值。

這些參數通常需要對應用程式 UI 進行大幅變更,因此 Android 平台為這些變更建構了專門的機制。這項機制就是「活動重建」

活動重建

發生設定變更時,系統會重新建立 Activity。系統會呼叫 onDestroy() 並刪除現有的 Activity 例項,接著使用 onCreate() 建立新的例項。這個新的 Activity 例項會以更新後的新設定進行初始化。也就是說,系統也會使用新設定重建使用者介面。

重建行為可讓應用程式根據與新裝置設定相符的替代資源自動重新載入應用程式,協助應用程式適應新設定

重建範例

假設 TextView 會使用 android:text="@string/title 顯示靜態標題,如版面配置 XML 檔案所定義。檢視區塊會在建立時根據目前語言,僅設定一次文字。如果語言有變,系統會重新建立活動。因此,系統也會重建檢視畫面,並根據新語言將檢視畫面初始化為正確的值。

重建作業也會清除在 Activity 或其任何內含 Fragment、View 和其他物件中,以欄位形式保留的任何狀態。這是因為「活動重建」會建立全新的 Activity 例項和 UI。此外,舊的 Activity 不會再顯示且已失效,因此任何對該活動或其內含物件的參照均已過時。這些參照可能會導致發生錯誤、記憶體流失和當機情形。

使用者的期望

應用程式的使用者會預期狀態能夠保留。如果使用者正在填寫表單,並且以多視窗模式開啟另一個應用程式來參照資訊,則當使用者返回到已清除內容的表單,或返回到應用程式的其他位置,就會造成使用者體驗不佳。您必須在設定變更到活動重建的過程中,確保使用者能享有一致的體驗。

如要驗證應用程式是否能保留狀態,可以在應用程式於前景和背景運作時,執行會導致設定變更的動作。這些動作包括:

  • 旋轉裝置
  • 進入多視窗模式
  • 在多視窗模式或任意形式視窗中調整應用程式大小
  • 折疊搭配多個螢幕的折疊式裝置
  • 變更系統主題,例如深色模式與淺色模式
  • 變更字型大小
  • 變更系統或應用程式語言
  • 連接或拔除硬體鍵盤
  • 連接或拔除座架

您可以採用 3 種主要方式,透過活動重建來保留相關狀態。其相關性視您想保留的狀態類型而定:

  • 本機常駐性可用於處理複雜或大型資料的程序終止。常駐性本機儲存空間包含資料庫或 DataStore
  • 保留的物件 (例如 ViewModel),可在使用者積極使用應用程式時,處理記憶體中的 UI 相關狀態。
  • 儲存的例項狀態,用於處理系統啟動的程序終止,並根據使用者輸入內容或瀏覽情形保留暫時狀態。

儲存 UI 狀態」頁面會詳細說明每種方式適用的 API,並逐一解釋適用時機。

限制活動重建

您可以避免系統為特定設定變更自動重建活動。活動重建會導致系統重新建立整個 UI,以及衍生自 Activity 的所有物件。您可能有充分的理由來避免這種情況。舉例來說,應用程式可能不需要在特定設定變更期間更新資源,或者設有效能限制。在此情況下,您可以宣告活動會自行處理設定變更,避免系統重新啟動活動。

您可以針對特定設定變更停用活動重建功能,方法是在 AndroidManifest.xml<activity> 項目中,將設定類型加入 android:configChanges。可能的值會列在 android:configChanges 屬性的說明文件中。

以下資訊清單程式碼會在螢幕方向和鍵盤可用性變更時,停用 MyActivity 的活動重建功能:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

有些設定變更一律會導致系統重新啟動活動,您無法予以停用。例如,您無法停用 API 32 中導入的動態色彩變更。日後可能會有更多設定變更以類似方式運作。

回應 View 系統中的設定變更

在 View 系統中,當發生設定變更而您已停用活動重建功能時,該活動會收到對 Activity.onConfigurationChanged() 的呼叫。所有附加的檢視畫面也會收到對 View.onConfigurationChanged() 的呼叫。如果是尚未新增到 android:configChanges 的設定變更,系統會照常重新建立活動。

onConfigurationChanged() 回呼方法會接收指定新裝置設定的 Configuration 物件。讀取 Configuration 物件中的欄位,判斷新的設定內容。如要進行後續變更,請更新介面所用的資源。當系統呼叫此方法時,系統會更新活動的 Resources 物件,根據新設定傳回資源。這樣您就能重設 UI 元素,不需要系統重新啟動活動。

舉例來說,下列 onConfigurationChanged() 實作會檢查是否有任何可用的鍵盤:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

如果不需要根據這些設定變更更新應用程式,您可以改為不實作 onConfigurationChanged()。如果是這種情況,所有在設定變更前使用的資源都仍會繼續使用,只是避免重新啟動活動而已。舉例來說,在連接或卸離藍牙鍵盤時,電視應用程式可能不需要回應。

保留狀態

使用這項技巧時,您仍須在一般活動生命週期中保留狀態。可能的原因如下:

  • 不可避免的變更:無法避免的設定變更可能會重新啟動應用程式。
  • 程序終止:應用程式應能處理系統啟動的程序終止。如果使用者退出應用程式,且應用程式進入背景,系統可能會刪除該應用程式。

回應 Jetpack Compose 中的設定變更

Jetpack Compose 可讓應用程式更輕鬆地回應設定變更。但是,如果在可行的情況下為所有設定變更停用活動重建功能,您仍必須確保應用程式能正確處理設定變更。

使用 LocalConfiguration 本機組合時,Compose UI 階層可適用 Configuration 物件。每次此物件變更時,從 LocalConfiguration.current 讀取的可組合函式都會重組。如要瞭解本機組合的運作方式,請參閱「使用 CompositionLocal 的本機範圍資料」說明文件。

範例

在以下範例中,可組合項會顯示具有特定格式的日期。該可組合函式會使用 LocalConfiguration.current 呼叫 ConfigurationCompat.getLocales(),回應系統語言代碼設定的變更。

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

為避免在語言代碼變更時重新建立活動,代管 Compose 程式碼的 Activity 需要選擇停用語言代碼設定變更。如要這樣做,請將 android:configChanges 設為 locale|layoutDirection

設定變更的重要概念和想法

總而言之,處理設定變更時,您需要瞭解下列重要概念:

  • 設定:裝置設定會定義 UI 應向使用者顯示的方式,例如應用程式顯示大小、語言代碼或系統主題。
  • 設定變更:設定會隨著使用者互動而變更。例如,使用者可能會變更裝置設定或與裝置實際互動的方式。設定會變更,而且無法「防止」設定變更。
  • 活動重建:在預設情況下,設定變更會導致活動重建。這項內建機制可針對新設定重新初始化應用程式狀態。
  • 活動刪除:活動重建會導致系統刪除舊 Activity 例項並建立新的例項。舊例項現已過時;任何剩餘的參照都會導致記憶體流失、發生錯誤或當機。
  • 狀態:舊 Activity 例項中的狀態不會顯示在新 Activity 例項中,因為這是兩個不同的物件例項。請保留應用程式和使用者的狀態,如「儲存 UI 狀態」一文所述。
  • 選擇停用:為特定設定變更類型選擇停用活動重建功能,是一種潛在的最佳化機制。您必須確保應用程式能正確更新,回應新的設定。

最佳做法

為提供良好的使用者體驗,請觀察下列事項:

  • 設定經常變動:無論 API 級別、板型規格或 UI 工具包為何,請勿假設很少或完全不會發生設定變更。 當使用者造成設定變更時,會期望應用程式能夠更新並繼續根據新設定正確運作。
  • 保留狀態:在活動重建發生時,不要遺失使用者的狀態。請保留狀態,如「儲存 UI 狀態」一文所述。
  • 避免將選擇停用做為快速修正方式:請勿將選擇停用活動重建功能,當做避免失去狀態的捷徑。停用活動重建功能必須履行處理變更的承諾,而您可能會因為其他設定變更、程序終止或關閉應用程式的活動重建而遺失狀態。因此,您無法完全停用活動重建功能。請保留狀態,如「儲存 UI 狀態」一文所述。
  • 不要避免設定變更:不要為了避免設定變更或活動重建,對螢幕方向、顯示比例或是否可調整大小設下限制。這會對想以自己偏好方式使用應用程式的使用者產生負面影響。

處理以大小為依據的設定變更

以大小為依據的設定變更隨時都會發生。如果應用程式在使用者可進入多視窗模式大螢幕裝置上執行,這種情況會更頻繁地發生。使用者會預期應用程式能夠在該環境中順利運作。

大小變更分為兩種一般類型:大幅變更和小幅變更。「大幅」的大小變更是指由於螢幕大小的差異 (例如寬度、高度或最小寬度),將不同額外資源組合套用到新設定的情形。這些資源包括應用程式本身定義的資源,以及任何包含資源的程式庫。

針對以大小為依據的設定變更限制活動重建

如果您針對以大小為依據的設定變更停用活動重建功能,系統不會重新建立 Activity,而會改為接收對 Activity.onConfigurationChanged() 的呼叫。所有附加的檢視畫面都會收到對 View.onConfigurationChanged() 的呼叫。

允許針對以大小為依據的設定變更重建活動

在 API 24 以上級別中,只有在發生大幅變更時,系統才會針對以大小為依據的設定變更重建活動。如果系統因為大小不足而未重建活動,可能會改為呼叫 Activity.onConfigurationChanged()View.onConfigurationChanged()

關於 Activity 和 View 回呼,請注意以下幾點:

  • API 30 以上級別不會呼叫 Activity.onConfigurationChanged() 回呼。
  • API 32 和 API 33 不會呼叫 View.onConfigurationChanged()。這項錯誤在日後的 API 版本中已獲修正。

如果程式碼需要監聽以大小為依據的設定變更,建議您使用公用程式 View 搭配覆寫的 View.onConfigurationChanged(),而不要依賴 Activity 重建或 Activity.onConfigurationChanged()