处理配置变更
某些设备配置可能会在运行时发生变化(例如屏幕方向以及当用户启用多窗口模式时)。发生这种变化时,android会重启正在运行的
Activity
(先后调用onDestroy()
和onCreate()
)。重启行为旨在通过利用与新设备配置相匹配的备用资源来自动重新加载应用,从而帮助它适应新配置。
如要妥善处理重启行为,
Activity
必须恢复其先前的状态。可以同时使用onSaveInstanceState()
、ViewModel
对象以及持久存储,从而在配置变更时保存并恢复Activity
的界面状态。如需详细了解如何保存Activity
状态,请阅读保存界面状态。
如要测试应用能否在保持完好状态的情况下自行重启,应在执行应用内的各类任务时主动更改配置(例如,改变屏幕方向)。你的应用应该能够在不丢失用户数据或状态的情况下随时重启,以便处理如下事件:配置发生了变化,或者用户收到来电并在应用进程被销毁很久之后返回应用。如要了解如何恢复Activity
状态,请阅读Activity生命周期。
然而,可能会遇到这种情况:重启应用并恢复大量数据不仅成本高昂,而且会造成糟糕的用户体验。在此情况下,还有两个选择:
- 在配置变更期间保留对象。允许
Activity
在配置变更时重启,但是需要将带有状态的对象传递给Activity
的新实例。- 自行处理配置变更。由于处理配置变更的实际过程较为复杂,因此不建议自行处理配置变更。不过,如果无法使用首选项(
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)及以上版本的系统。screenLayout
值是必要的,以检测可折叠手机和可转换的chromebook等设备可能触发的变化。keyboardHidden
值可在键盘可用性发生变更时阻止重启。
如果想在应用程序中手动处理方向的改变,必须在
android:configChanges
属性中声明orientation
、screenSize
和screenLayout
的值。通过使用管道|
字符分隔,可以在属性中声明多个配置值。
例如,以下清单文件代码所声明的Activity
可同时处理屏幕方向变更和键盘可用性变更:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
现在,即便其中某个配置发生变化,
MyActivity
也不会重启。但MyActivity
会接收到对onConfigurationChanged()
的调用消息。此方法会收到传递的Configuration
对象,从而指定新设备配置。可以通过读取Configuration
中的字段确定新配置,然后通过更新界面所用资源进行适当的更改。调用此方法时,Activity
的Resources
对象会相应地进行更新,并根据新配置返回资源,以便在系统不重启Activity
的情况下轻松重置界面元素。
例如,以下onConfigurationChanged()
实现用于检查当前的设备方向:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// 检查屏幕方向
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
生命周期中逃避保留状态的方法。应用程序应始终能够在其先前状态不变的情况下关闭和重新启动。无法阻止的配置更改可以重新启动应用程序。如果用户离开应用,并且其被放置在后台,系统可能会销毁应用程序。