根据状态提升到的位置和所需的逻辑,您可以使用不同的 API 来存储和恢复您的UI 状态。每个应用都使用 API 的组合来最好地实现这一点。
任何 Android 应用都可能由于 Activity 或进程重建而丢失其UI 状态。状态丢失可能由以下事件引起
在这些事件发生后保留状态对于提供良好的用户体验至关重要。选择要持久化的状态取决于应用的独特用户流程。最佳实践是至少保留用户输入和与导航相关的状态。例如,列表的滚动位置、用户想要查看详细信息的项目的 ID、用户正在进行的偏好选择或文本字段中的输入。
此页面总结了根据状态提升到的位置以及需要状态的逻辑,可用于存储 UI 状态的 API。
UI 逻辑
如果您的状态提升到 UI 中,无论是可组合函数还是作用域限定到 Composition 的普通状态持有者类,您都可以使用rememberSaveable
在 Activity 和进程重建之间保留状态。
在以下代码片段中,rememberSaveable
用于存储单个布尔型 UI 元素状态
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails
是一个布尔型变量,用于存储聊天气泡是折叠还是展开。
rememberSaveable
通过已保存实例状态机制将UI 元素状态存储在Bundle
中。
它能够自动将基本类型存储到 Bundle 中。如果您的状态保存在非基本类型中(例如数据类),则可以使用不同的存储机制,例如使用Parcelize
注解,使用 Compose API(如listSaver
和mapSaver
),或实现扩展 Compose 运行时Saver
类的自定义 Saver 类。请参阅存储状态的方法文档,以了解有关这些方法的更多信息。
在以下代码片段中,rememberLazyListState
Compose API 使用rememberSaveable
存储LazyListState
,它包含LazyColumn
或LazyRow
的滚动状态。它使用LazyListState.Saver
,这是一个能够存储和恢复滚动状态的自定义 Saver。在 Activity 或进程重建后(例如,在配置更改(如更改设备方向)后),滚动状态将被保留。
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
最佳实践
rememberSaveable
使用Bundle
存储 UI 状态,该状态由其他也写入它的 API 共享,例如 Activity 中的onSaveInstanceState()
调用。但是,此Bundle
的大小是有限制的,存储大型对象可能会导致运行时出现TransactionTooLarge
异常。这在单个Activity
应用中尤其成问题,因为整个应用都在使用同一个Bundle
。
为了避免此类崩溃,不应在 Bundle 中存储大型复杂对象或对象列表。
相反,应存储所需的最小状态(如 ID 或键),并使用它们将恢复更复杂的 UI 状态委派给其他机制,例如持久性存储。
这些设计选择取决于应用的具体用例以及用户期望应用的行为方式。
验证状态恢复
您可以验证在 Activity 或进程重新创建时,Compose 元素中使用rememberSaveable
存储的状态是否已正确恢复。有一些特定的 API 可以实现此目的,例如StateRestorationTester
。请查看测试文档以了解更多信息。
业务逻辑
如果您的UI 元素状态提升到ViewModel
,因为业务逻辑需要它,您可以使用ViewModel
的 API。
在 Android 应用中使用ViewModel
的主要优势之一是它可以免费处理配置更改。当发生配置更改并且 Activity 被销毁并重新创建时,提升到ViewModel
的 UI 状态将保留在内存中。重建后,旧的ViewModel
实例将附加到新的 Activity 实例。
但是,ViewModel
实例无法在系统触发的进程终止后继续存在。若要使 UI 状态在这种情况后继续存在,请使用ViewModel 的 Saved State 模块,其中包含SavedStateHandle
API。
最佳实践
SavedStateHandle
也使用Bundle
机制存储 UI 状态,因此您应该只使用它来存储简单的UI 元素状态。
通过应用业务规则并访问除 UI 之外的应用层产生的屏幕 UI 状态,不应存储在SavedStateHandle
中,因为它可能很复杂且很大。您可以使用不同的机制来存储复杂或大型数据,例如本地持久性存储。进程重建后,屏幕将使用存储在SavedStateHandle
(如果存在)中的已恢复瞬态状态重新创建,然后从数据层重新生成屏幕 UI 状态。
SavedStateHandle
API
SavedStateHandle
有不同的 API 用于存储 UI 元素状态,最值得注意的是
Compose State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Compose State
使用SavedStateHandle
的saveable
API 将 UI 元素状态读写为MutableState
,以便它在最少的代码设置下在 Activity 和进程重建后继续存在。
saveable
API 开箱即用地支持基本类型,并接收stateSaver
参数以使用自定义 Saver,就像rememberSaveable()
一样。
在以下代码片段中,message
将用户输入类型存储到TextField
中
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
有关使用saveable
API 的更多信息,请参阅SavedStateHandle
文档。
StateFlow
使用getStateFlow()
存储 UI 元素状态并将其作为流从SavedStateHandle
中获取。 StateFlow
是只读的,并且 API 需要您指定一个键,以便您可以替换流以发出新值。使用您配置的键,您可以检索StateFlow
并收集最新值。
在以下代码片段中,savedFilterType
是一个StateFlow
变量,用于存储应用于聊天应用中聊天频道列表的筛选器类型
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
每次用户选择新的过滤器类型时,都会调用 setFiltering
。这会将新值保存在 SavedStateHandle
中,并使用键 _CHANNEL_FILTER_SAVED_STATE_KEY_
进行存储。 savedFilterType
是一个 Flow,它会发出存储到该键的最新值。 filteredChannels
订阅了该 Flow 以执行频道过滤。
有关 getStateFlow()
API 的更多信息,请参阅 SavedStateHandle
文档。
摘要
下表总结了本节中介绍的 API,以及何时使用每个 API 来保存 UI 状态。
事件 | UI 逻辑 | ViewModel 中的业务逻辑 |
---|---|---|
配置更改 | rememberSaveable |
自动 |
系统发起的进程死亡 | rememberSaveable |
SavedStateHandle |
要使用的 API 取决于状态的保存位置及其所需的逻辑。对于在 UI 逻辑 中使用的状态,请使用 rememberSaveable
。对于在 业务逻辑 中使用的状态,如果将其保存在 ViewModel
中,请使用 SavedStateHandle
保存它。
您应该使用 bundle API(rememberSaveable
和 SavedStateHandle
)来存储少量 UI 状态。此数据是将 UI 恢复到其先前状态所需的最小必要数据,以及其他存储机制。例如,如果在 bundle 中存储用户正在查看的个人资料 ID,则可以从数据层获取重量级数据(如个人资料详细信息)。
有关保存 UI 状态的不同方法的更多信息,请参阅通用 保存 UI 状态文档 和架构指南的 数据层 页面。
推荐内容
- 注意:当 JavaScript 关闭时,将显示链接文本。
- 在何处提升状态
- 状态和 Jetpack Compose
- 列表和网格