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 应用在面对配置更改和进程终止时保持一致的用户体验。

相关推荐
蹦哒13 小时前
Kotlin 与 Java 语法差异
java·python·kotlin
Kapaseker16 小时前
五分钟搞定 Compose 的打字机效果
android·kotlin
Fate_I_C17 小时前
Android现代开发:Kotlin&Jetpack
android·开发语言·kotlin·android jetpack
XiaoLeisj18 小时前
Android Kotlin 全链路系统化指南:从基础语法、类型系统与面向对象,到函数式编程、集合操作、协程并发与 Flow 响应式数据流实战
android·开发语言·kotlin·协程
RainyJiang1 天前
谱写Kotlin协程面试进行曲-进阶篇(第二乐章)
面试·kotlin·android jetpack
星霜笔记1 天前
GitMob — 手机端 GitHub 管理工具
android·kotlin·github·android jetpack
android_cai_niao2 天前
OkHttp 使用教程:从入门到精通(Kotlin)
okhttp·kotlin
Yang-Never3 天前
OpenGL ES ->YUV图像基础知识
android·java·开发语言·kotlin·android studio
idealzouhu3 天前
【Kotlin】 数据流完全指南:冷流、热流与 Android 实战
android·开发语言·kotlin
常利兵3 天前
Android 字体字重设置:从XML到Kotlin的奇妙之旅
android·xml·kotlin