某些设备配置可能会在应用运行期间发生变更。这些变更包括但不限于:
- 应用显示大小
- 屏幕方向
- 字体大小和粗细
- 语言区域
- 深色模式与浅色模式
- 键盘可用性
其中大部分配置变更都是由某些用户互动触发的。例如,旋转或折叠设备会改变应用可用的屏幕空间量。同样,更改设备设置(例如字体大小、语言或首选主题)也会改变 Configuration 对象中的相应值。
这些参数通常需要对应用界面进行充分的更改。因此,Android 平台提供了一种专有机制来处理这种更改。这种机制就是 Activity 重新创建。
activity 重新创建
当发生配置变更时,系统会重新创建 Activity。为此,系统会调用 onDestroy 并销毁现有的 Activity 实例。随后,系统会使用 onCreate 创建一个新实例,并且这个新的 Activity 实例会使用更新后的新配置进行初始化。这也意味着,系统还会使用新配置重新创建界面。
通常,Activity 会充当可组合项的宿主。当 Activity 重新创建时,Compose 也会使用新的配置值重新创建界面。
重新创建行为会自动利用与新设备配置相匹配的备用资源来自动重新加载应用,从而帮助它适应新配置。
重新创建示例
假设有一个可组合项使用字符串资源显示静态标题:
// In the res/values/strings.xml file // <string name="compose">Jetpack Compose</string> // In your Compose code Text( text = stringResource(R.string.compose) )
创建 Activity 时,Text 可组合项会读取当前配置(例如语言),并解析相应的字符串资源。
如果语言发生更改,系统会重新创建 activity。发生这种情况时,Compose 会重新创建界面。由于 stringResource 从当前配置中读取,因此标题会自动更新为正确的本地化值。
重新创建过程还会清除您在 Activity 中以字段形式保留的任何状态。
如需在配置发生更改后保留界面状态,请使用建议的状态管理模式。使用 ViewModel 处理数据和业务逻辑,使用 rememberSaveable 处理界面级状态。借助这些机制,您的状态可以在 Activity 重新创建时保持不变,同时界面会更新以反映新配置。
如需详细了解如何在 Compose 中保存状态,请参阅在 Compose 中保存界面状态。
用户期望
应用用户会希望保留状态。如果用户正在填写表单,并在多窗口模式下打开另一个应用来参考信息,却在返回表单时发现表单已清空,或者直接跳转至应用的其他位置,那么用户体验将会非常糟糕。作为开发者,您必须确保在配置变更和 activity 重新创建的整个过程中提供一致的用户体验。
如需验证应用中是否保留了状态,您可以在应用处于前台和后台时执行会导致配置变更的操作。这些操作包括:
- 旋转设备
- 进入多窗口模式
- 在多窗口模式或自由窗口模式下调整应用大小
- 折叠具有多个显示屏的可折叠设备
- 更改系统主题,例如深色模式与浅色模式
- 更改字体大小
- 更改系统或应用语言
- 连接或断开硬件键盘
- 连接或断开基座
您可以采用多种方法在重新创建 Activity 的过程中保留相关状态。采用哪种方法取决于您要保留的状态类型:
- 本地持久性存储可用于处理复杂或大型数据的进程终止。持久性本地存储包括数据库或
DataStore。 - 保留的对象(如
ViewModel实例)可在用户正在使用应用时,处理内存中与界面相关的状态。 rememberSaveable,以便在配置更改和系统启动的进程终止后保留瞬时界面状态。这适用于取决于用户输入、滚动位置或导航但又不属于ViewModel的状态。
如需详细了解各个 API 以及各自适用的使用场景,请参阅保存界面状态。
限制 activity 重新创建
您可以禁止在发生特定配置变更时自动重新创建 activity。在仅使用 Compose 的现代应用中,无论哪种方式,界面都会重新组合,但建议直接处理配置变更。
默认情况下,配置变更会强制系统销毁并重新创建 activity,包括界面和从 activity 派生的任何对象。如果您声明由 activity 来自行处理配置变更,系统会阻止这种情况。相反,只有 Configuration 对象会更新,而 Compose 会使用新值重新组合界面。
直接在 Compose 中处理配置变更具有多项优势:
- 提升了性能:重新组合界面比完整的 Activity 重新创建周期成本更低,尤其是在进行细微更改时。
- 流畅的动画:避免重新启动 activity 可让您在配置更改期间运行连续动画,例如在设备旋转期间实现平滑的布局过渡。
- 状态保留:保留 Activity 实例可降低在屏幕旋转等事件期间丢失临时界面状态的风险。请注意,您仍必须处理系统发起的进程终止时的状态保留问题。
如需针对特定配置变更停用 activity 重新创建功能,请在 AndroidManifest.xml 文件的 <activity> 条目中将相应配置类型添加到 android:configChanges。android:configChanges 属性文档中介绍了可能的值。
在屏幕方向和键盘可用性发生更改时,以下清单代码会为 MyActivity 停用 Activity 重新创建功能:
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:label="@string/app_name">
Android 17
从 Android 17(API 级别 37)开始,对于通常不需要完全重新创建界面的几项配置变更,系统默认不再重新启动 activity。相反,activity 会保持活跃状态,并通过 onConfigurationChanged() 回调接收更新。
具体配置更改包括:
CONFIG_KEYBOARDCONFIG_KEYBOARD_HIDDENCONFIG_NAVIGATIONCONFIG_TOUCHSCREENCONFIG_COLOR_MODECONFIG_UI_MODE(仅当界面模式更改为UI_MODE_TYPE_DESK或从UI_MODE_TYPE_DESK更改为其他类型时)
如果您的应用依赖于完全重启来重新加载资源以实现这些更改,您现在必须在清单文件中使用 android:recreateOnConfigChanges 属性来明确选择启用旧行为。借助该属性,您可以指定哪些配置更改应触发完整的 activity 停止、销毁和重新创建周期,例如:
<activity
android:name=".MyActivity"
android:recreateOnConfigChanges="keyboard|keyboardHidden|navigation|colorMode|touchscreen|...">
...
</activity>
对配置变更做出响应
Jetpack Compose 可让应用更轻松地响应配置变更。不过,如果您尽可能为所有配置变更停用 Activity 重新创建功能,应用仍然需要正确处理配置变更。
可以在具有 LocalConfiguration CompositionLocal 的 Compose 界面层次结构中使用 Configuration 对象。每当发生变更时,从 LocalConfiguration.current 进行读取的可组合函数都会重组。如需详细了解 CompositionLocal 的运行方式,请参阅使用 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))
)
}
为避免在发生语言区域更改时重新创建 Activity,托管 Compose 代码的 Activity 需要停用语言区域配置变更。为此,请将 android:configChanges 设置为 locale|layoutDirection。
配置变更:关键概念和最佳实践
在处理配置变更时,您需要了解以下关键概念:
- 配置:设备配置用于定义界面如何向用户显示内容,例如应用显示大小、语言区域或系统主题。在 Compose 中,您可以使用
LocalConfiguration访问配置值。 - 配置变更:配置会根据用户互动发生变更。例如,用户可能会更改设备设置或与设备的物理互动方式。您无法阻止配置变更。
Activity重新创建:默认情况下,配置变更会导致重新创建Activity。这是为新配置重新初始化应用状态的内置机制。Activity销毁:Activity重新创建会导致系统销毁旧的Activity实例,并创建一个新实例来代替它。旧实例现已过时。避免在生命周期范围之外保留对生命周期范围对象的引用。- 状态:旧
Activity实例中的状态不存在于新Activity实例中,因为它们是两个不同的对象实例。请勿将状态与 activity 绑定,而是使用推荐的 API 来保留应用和用户状态,如保存界面状态中所述。 - 停用:为某种类型的配置变更停用 activity 重新创建功能需要确保应用根据新配置进行正确更新。
为了提供良好的用户体验,请遵循以下最佳实践:
- 为配置频繁变更做好准备:不要认为配置变更会很少发生或从不发生,无论 API 级别、设备规格或界面工具包如何。当用户导致配置变更时,他们会希望应用进行更新,并继续使用新配置正常运行。
- 保留状态:在重新创建
Activity时,不要丢失用户的状态。使用ViewModel和rememberSaveable等 API 按照保存界面状态中的说明保留状态。 - 避免停用快速修复功能:不要停用
Activity重新创建功能,这样可以轻松避免丢失状态。停用 activity 重新创建功能需要实现处理变更的承诺,而您可能会因为其他配置变更、进程终止或应用关闭所带来的Activity重新创建而丢失状态。因此,您无法完全停用Activity重新创建功能。请按照保存界面状态中描述的方法保留状态。 - 不要回避配置变更:不要为了回避配置变更和
Activity重新创建,而对屏幕方向、宽高比或尺寸可调整性施加限制。这会对想要按照自己首选方式使用应用的用户产生负面影响。
处理基于大小的配置变更
基于大小的配置可能会随时发生变更;而且当应用在用户可以进入多窗口模式的大屏设备上运行时,发生这种变更的可能性会更高。用户会希望应用能够在该环境中正常运行。
大小变更通常分为两类:显著变更和细微变更。显著的大小变更是指由于屏幕尺寸不同(例如宽度、高度或最小宽度)而将一组不同的备用资源应用于新配置。这些资源包括应用自行定义的资源,以及从该应用所用的任何库中获取的资源。
限制为基于大小的配置变更重新创建 activity
为基于大小的配置变更停用 Activity 重新创建功能后,系统不会重新创建 Activity,而是会收到对 Activity.onConfigurationChanged 的调用。读取 LocalConfiguration.current 的任何可组合项都会自动重组以反映新的大小。
如果您的清单文件中包含 android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout",系统为会基于大小的配置变更停用 Activity 重新创建功能。
其他资源
如需详细了解如何处理配置更改,请参阅以下其他资源: