7、保存界面状态

保存界面状态

在 Jetpack Compose 应用中,正确保存和恢复界面状态对于提供良好的用户体验至关重要。当应用因重新创建 activity 或进程而丢失界面状态时,用户可能会遇到数据丢失或界面不一致的问题。本指南将详细介绍如何在 Jetpack Compose 中保存和恢复界面状态。


1. 界面状态丢失的原因

Android 应用可能会因以下事件而丢失界面状态:

  • 系统发起的进程终止:如内存不足时系统杀掉进程。
  • 配置更改:如屏幕旋转、语言切换等。
  • 用户发起的进程终止:如用户显式关闭应用。

对于用户发起的进程终止,瞬时状态的丢失通常是合理的。但在其他情况下,保留状态对于提供流畅的用户体验至关重要。


2. 保存界面状态的策略

根据状态提升的位置和所需的逻辑,可以使用不同的 API 来保存和恢复界面状态。

2.1 界面逻辑中的状态保存

当状态在界面逻辑中提升时(无论是在可组合函数还是作用域限定为组合的普通状态容器类中),可以使用 rememberSaveable 在重新创建 activity 和进程之后保留状态。

示例:保存单个布尔值界面元素状态

kotlin 复制代码
@Composable
fun ChatBubble(message: Message) {
    var showDetails by rememberSaveable { mutableStateOf(false) }
    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )
    if (showDetails) {
        Text(message.timestamp)
    }
}

存储机制

  • rememberSaveable 通过保存的实例状态机制将界面元素状态存储在 Bundle 中。
  • 它能够自动将基元类型存储到 Bundle 中。对于非基元类型(如数据类),可以使用 Parcelize 注解、listSavermapSaver 等 Compose API,或实现自定义的 Saver 类。

示例 :保存 LazyListState

kotlin 复制代码
@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset)
    }
}

2.2 业务逻辑中的状态保存

当界面元素状态因业务逻辑需要被提升到 ViewModel 时,可以使用 ViewModel 的 API 来保存状态。

ViewModel 的优势

  • ViewModel 实例在配置更改(如屏幕旋转)后仍然存在,因此提升到 ViewModel 的界面状态会保留在内存中。
  • 但 ViewModel 实例在系统发起的进程终止后将失效。为了持久化状态,可以使用 SavedStateHandle

示例 :使用 SavedStateHandle 保存界面元素状态

kotlin 复制代码
class ConversationViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
    private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }
}

@Composable
fun UserInput(viewModel: ConversationViewModel = viewModel()) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

SavedStateHandle 的 API

  • saveable API :以 MutableState 形式读取和写入界面元素状态,支持基元类型和自定义 Saver
  • getStateFlow API :存储界面元素状态,并将其用作来自 SavedStateHandle 的数据流。

3. 最佳实践

3.1 界面逻辑中的最佳实践

  • 避免存储大型对象rememberSaveable 使用 Bundle 存储界面状态,Bundle 大小有限。存储大型复杂对象或对象列表可能会导致 TransactionTooLargeException
  • 存储最少必需状态:仅存储恢复界面状态所需的最少数据(如 ID 或键),并使用这些数据从数据层重新生成复杂状态。

3.2 业务逻辑中的最佳实践

  • 使用 SavedStateHandle 存储简单状态SavedStateHandle 也使用 Bundle 机制,因此应仅用于存储简单的界面元素状态。
  • 复杂状态使用永久性存储 :屏幕界面状态可能非常复杂且庞大,不应存储在 SavedStateHandle 中。可以使用本地永久性存储(如 Room 数据库)来存储复杂数据。

4. 验证状态恢复

为了验证重新创建 activity 或进程后,Compose 元素中使用 rememberSaveable 存储的状态是否已正确恢复,可以使用 StateRestorationTester

示例 :使用 StateRestorationTester 验证状态恢复

kotlin 复制代码
@Test
fun testStateRestoration() {
    val tester = StateRestorationTester(context)
    tester.setContent {
        val (value, setValue) = rememberSaveable { mutableStateOf(0) }
        // Test logic here
    }
}

5. 总结

  • 界面逻辑中的状态 :使用 rememberSaveable 在重新创建 activity 和进程后保留状态。
  • 业务逻辑中的状态 :如果状态被提升到 ViewModel,使用 SavedStateHandle 保存状态。
  • 最佳实践:避免在 Bundle 中存储大型复杂对象,存储最少必需状态,并使用永久性存储处理复杂数据。

通过合理使用这些 API 和最佳实践,可以确保 Jetpack Compose 应用在面对配置更改和进程终止时保持一致的用户体验。

相关推荐
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z3 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream4 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam4 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker4 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin
Kapaseker5 天前
你搞得懂这 15 个 Android 架构问题吗
android·kotlin
zh_xuan6 天前
kotlin 高阶函数用法
开发语言·kotlin