处理配置变更

某些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性,以及当用户启用多窗口模式时)。发生这种变化时,Android 会重启正在运行的 Activity(先后调用 onDestroy()onCreate())。重启行为旨在通过利用与新设备配置相匹配的备用资源来自动重新加载您的应用,从而帮助它适应新配置。

如要妥善处理重启行为,activity 必须恢复其先前的状态。您可以同时使用 onSaveInstanceState()ViewModel 对象以及持久存储,以在配置变更时保存并恢复 activity 的界面状态。如需详细了解如何保存 activity 状态,请参阅保存界面状态

如要测试应用能否在保持完好状态的情况下自行重启,您应在执行应用内的各类任务时主动更改配置(例如,改变屏幕方向)。您的应用应能够在不丢失用户数据或状态的情况下随时重启,以便处理如下事件:配置发生变化,或者用户收到来电并在应用进程被销毁很久之后返回应用。如要了解如何恢复 activity 状态,请参阅 activity 生命周期

然而,您可能会遇到这种情况:重启应用并恢复大量数据不仅成本高昂,而且会造成糟糕的用户体验。在这种情况下,您还有另外两种选择:

  1. 在配置变更期间保留对象

    允许 activity 在配置变更时重启,但是需将有状态对象传递给 activity 的新实例。

  2. 自行处理配置变更

    由于处理配置变更的实际过程较为复杂,因此不建议您自行处理配置变更。不过,如果您无法使用首选项(onSaveInstanceState()ViewModel 和持久存储)来保留界面状态,则可阻止系统在特定配置变更期间重启您的 activity。配置变更时,应用会收到回调,以便您可以根据需要手动更新 activity。

在配置变更期间保留对象

如果重启 activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。此外,若使用系统通过 onSaveInstanceState() 回调为您保存的 Bundle,则可能无法完全恢复 activity 状态,因为该类并非用于携带大型对象(例如位图),并且其中的数据必须依次在主线程中进行序列化和反序列化,而这可能会消耗大量内存并降低配置变更的速度。在这种情况下,您可通过使用 ViewModel 对象来减轻重新初始化 activity 的负担。系统会在配置变更时保留 ViewModel 对象,使其成为保存界面数据的理想场所,让您无需再次查询这些数据。如需详细了解如何在应用中使用 ViewModel 类,请参阅 ViewModel 概览

自行处理配置变更

如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免 activity 重启,则可声明 activity 自行处理配置变更,从而阻止系统重启 activity。

注意:自行处理配置变更可能会提高使用备用资源的难度,因为系统不会为您自动应用这些资源。只有在必须避免 activity 因配置变更而重启的无奈情况下,您才可考虑使用此方法,并且不建议对大多数应用使用此方法。

如要声明由 activity 处理配置变更,请在清单文件中修改相应的 <activity> 元素,以包含 android:configChanges 属性,该属性的值表示要处理的配置。android:configChanges 属性文档中列出了该属性的可能值。最常用的值包括 "orientation""screenSize""screenLayout""keyboardHidden"

  • "orientation" 值可在屏幕方向发生变更时阻止重启。
  • "screenSize" 值也可在屏幕方向发生变更时阻止重启,但仅适用于 Android 3.2(API 级别 13)及以上版本的系统。
  • 如要检测可由可折叠手机和可翻转 Chromebook 等设备触发的变更,则需要使用 "screenLayout" 值。
  • "keyboardHidden" 值可在键盘可用性发生变更时阻止重启。

如要在应用中手动处理方向变更,您必须在 android:configChanges 属性中声明 "orientation""screenSize""screenLayout" 值。您可以在属性中声明多个配置值,方法是用管道 | 字符将其进行分隔。

例如,以下清单文件代码所声明的 activity 可同时处理屏幕方向变更和键盘可用性变更:

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

现在,某个配置发生变化时,MyActivity 不会重启。不过,MyActivity 会收到对 onConfigurationChanged() 的调用消息。

onConfigurationChanged() 方法会收到传递的 Configuration 对象,从而指定新设备配置。您可以通过读取 Configuration 对象中的字段确定新配置,然后通过更新界面所用资源进行适当的更改。调用此方法时,activity 的 Resources 对象会相应地进行更新,并根据新配置返回资源,以便您在系统不重启 activity 的情况下轻松重置界面元素。

例如,以下 onConfigurationChanged() 实现用于检查当前的设备方向:

Kotlin

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

    // Checks the orientation of the screen
    if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show()
    } else if (newConfig.orientation === Configuration.ORIENTATION_PORTRAIT) {
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show()
    }
}

Java

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

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration 对象代表所有当前配置,而不仅仅是已变更的配置。多数情况下,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为正在处理的配置提供备用资源。例如,由于 Resources 对象现已更新,您便可通过 setImageResource() 重置任何 ImageView 实例,并使用合适的新配置资源(如应用资源概览中所述)。

请注意,Configuration 字段中的值是与 Configuration 类中特定常量相匹配的整数。如需获取有关各个字段所用常量的记录,请参阅 Configuration 参考文档中的相应字段。

注意:在声明由 activity 处理配置变更时,您有责任重置要为其提供备用资源的所有元素。如果您声明由 activity 处理方向变更,且需要在横向和纵向之间切换某些图像,则您必须在 onConfigurationChanged() 期间为每个元素重新分配每项资源。

如果无需根据这些配置变更更新应用,则您可不必实现 onConfigurationChanged()在这种情况下,应用仍会使用配置变更前所用的全部资源,区别在于您无需重启 activity。

不过,请勿认为这种方法可以避免在正常的 activity 生命周期中保留状态。您的应用应始终能在保持先前状态完好的情况下关闭和重启。您无法阻止的配置变更可能会导致您的应用重启。如果用户离开您的应用,使其在后台运行,则系统可能会终止该应用。