根据状态提升的位置和所需的逻辑,您可以使用不同的 API 来存储和恢复您的UI 状态。每个应用都结合使用多种 API 来最好地实现这一点。
任何 Android 应用都可能因活动或进程重新创建而丢失其UI 状态。由于以下事件,可能会发生这种状态丢失
在这些事件之后保留状态对于获得良好的用户体验至关重要。选择要持久化的状态取决于您应用的独特用户流程。作为最佳实践,您至少应保留用户输入和与导航相关的状态。这包括列表的滚动位置、用户想要了解详细信息的项目的 ID、用户偏好的正在进行的选择或文本字段中的输入。
此页面总结了根据状态提升的位置和需要它的逻辑来存储 UI 状态的可用 API。
UI 逻辑
如果您的状态提升到 UI 中,无论是可组合函数还是作用域为 Composition 的普通状态持有者类,您都可以使用rememberSaveable
来保留跨活动和进程重新创建的状态。
在以下代码片段中,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。在活动或进程重新创建(例如,在配置更改后,例如更改设备方向)后,滚动状态将被保留。
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
最佳实践
rememberSaveable
使用Bundle
来存储 UI 状态,该状态由其他也写入它的 API 共享,例如您活动中的onSaveInstanceState()
调用。但是,此Bundle
的大小是有限制的,存储大型对象可能会导致运行时出现TransactionTooLarge
异常。这在单个Activity
应用中尤其成问题,因为同一个Bundle
在整个应用中都使用。
为了避免这种类型的崩溃,您不应在 bundle 中存储大型复杂对象或对象列表。
相反,应存储所需的最少状态(如 ID 或键),并使用它们将恢复更复杂的 UI 状态委派给其他机制,如持久性存储。
这些设计选择取决于您应用的具体用例以及用户期望其行为方式。
验证状态恢复
您可以验证使用rememberSaveable
在 Compose 元素中存储的状态在活动或进程重新创建时是否正确恢复。有一些特定的 API 可以实现这一点,例如StateRestorationTester
。查看测试文档以了解更多信息。
业务逻辑
如果您的UI 元素状态提升到ViewModel
是因为业务逻辑需要它,您可以使用ViewModel
的 API。
在 Android 应用中使用ViewModel
的主要好处之一是它可以免费处理配置更改。当发生配置更改并且活动被销毁和重新创建时,提升到ViewModel
的 UI 状态将保留在内存中。重新创建后,旧的ViewModel
实例将附加到新的活动实例。
但是,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
参数来使用自定义保存器,就像 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
以 Flow 的形式使用它。 StateFlow
是只读的,此 API 需要您指定一个键,以便您可以替换 Flow 以发出新值。使用您配置的键,您可以检索 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 恢复到其先前状态所需的最小数据,以及其他存储机制。例如,如果将用户正在查看的个人资料 ID 存储在 bundle 中,则可以从数据层获取繁重的数据(如个人资料详细信息)。
有关保存 UI 状态的不同方法的更多信息,请参阅常规的 保存 UI 状态文档 和架构指南的 数据层 页面。
推荐内容
- 注意:当 JavaScript 关闭时,将显示链接文本。
- 在哪里提升状态
- 状态和 Jetpack Compose
- 列表和网格