StateStore   Android Jetpack 的一部分。

StateStore 是一种类型安全的容器,可在整个应用中以 Compose State 的形式存储和公开共享的应用状态。

使用场景

通过创建应用级作用域的 StateStore,您可以在同一进程内的应用组件之间共享状态:

  • 多个 activity:如果您的应用同时显示多个 activity(例如电子邮件客户端中的收件箱 activity 和编辑器 activity),或者具有支持多实例的 activity(例如文档查看器 activity),您可以使用 StateStore 在它们之间共享信息。
  • activity 和服务:在 activity 与后台 CarAppService 或 AppFunction 之间同步状态。
  • activity 和 content provider 或 widget:在 activity 和主屏幕 widget 之间共享状态。

如果单例只需要内存中的 State<T>,而不需要键控查找或 DataStore 支持的进程终止恢复,您可以改为直接在单例上公开 mutableStateOf 属性。如需比较应用中的状态 API,请参阅在架构中选择合适的状态 API。

API 概览

androidx.appstate.statestore 软件包提供 StateStore 和 StateStoreKey 类:

package androidx.appstate.statestore

import androidx.compose.runtime.State
import kotlinx.serialization.Serializable

public class StateStore {
    public val keys: Set<StateStoreKey<*>>
    public fun <T> getState(
        stateKey: StateStoreKey<T>,
        defaultValue: T = stateKey.defaultValue,
    ): State<T>
    public fun <T> setState(stateKey: StateStoreKey<T>, value: T)
    public inline fun <T> updateState(
        stateKey: StateStoreKey<T>,
        defaultValue: T = stateKey.defaultValue,
        update: (T) -> T,
    )
}

@Serializable
public open class StateStoreKey<T>(
    public val defaultValue: T,
)

定义状态

为了确保在存储和检索值时类型安全,StateStore 使用 StateStoreKey 类来定义用于读取和写入值的键。将 StateStoreKey 定义 private 或 internal 保留在拥有该状态切片的管理器、代码库或模块中:

@Serializable
sealed interface FocusSession {
    @Serializable
    data object Idle : FocusSession

    @Serializable
    data class Running(
        val taskId: String,
        val endTimeMillis: Long,
    ) : FocusSession

    @Serializable
    data class Paused(
        val taskId: String,
        val remainingMillis: Long,
    ) : FocusSession
}

@Serializable
private data object FocusSessionKey :
    StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle)

创建和注入 StateStore 实例

StateStore 是一个常规类,您可以在应用或模块范围(例如,在 Application 类或依赖项注入模块中)实例化一次,并注入到读取和写入状态的管理器或代码库中,而不是直接向界面类公开 StateStore:

// Instantiate at the application or DI module level
val stateStore = StateStore()

// Inject into managers or repositories that encapsulate their own keys
class FocusSessionManager(
    private val stateStore: StateStore,
)

读写状态

在管理器或代码库中,使用私有 StateStoreKey 定义读取、写入和更新注入的 StateStore 中的值,并向调用方公开只读 State<T> 属性和变更函数。

阅读状态

使用 getState(stateKey, defaultValue) 检索 State<T> 对象。如果密钥尚未初始化,StateStore 会使用 defaultValue 参数(默认为 stateKey.defaultValue)填充该密钥(或者您可以在初始化未填充的密钥时传递自定义 defaultValue)。由于 getState 会返回 State<T>,因此 ViewModel、界面可组合项、微件和 transform 代码块可以被动读取管理器的属性:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)
}

class FocusTimerViewModel(
    focusSessionManager: FocusSessionManager,
) : ViewModel() {
    val session: State<FocusSession> = focusSessionManager.session
}

@Composable
fun FocusBanner(viewModel: FocusTimerViewModel = viewModel()) {
    // Reads state reactively; recomposes when FocusSessionKey updates
    when (val session = viewModel.session.value) {
        FocusSession.Idle -> Text(text = "No active focus session")
        is FocusSession.Running -> Text(text = "Focusing on ${session.taskId}")
        is FocusSession.Paused -> Text(text = "Paused: ${session.taskId}")
    }
}

写入状态

在管理器中使用 setState(stateKey, value) 初始化或替换与键关联的值:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)

    fun startFocus(taskId: String, endTimeMillis: Long) {
        stateStore.setState(
            FocusSessionKey,
            FocusSession.Running(
                taskId = taskId,
                endTimeMillis = endTimeMillis,
            ),
        )
    }

    fun endFocus() {
        stateStore.setState(FocusSessionKey, FocusSession.Idle)
    }
}

更新状态

如果您需要根据当前值更新状态,请使用 updateState。 这会检索当前值(如果未设置,则使用默认值),并应用更新 lambda:

class FocusSessionManager(
    private val stateStore: StateStore,
) {
    val session: State<FocusSession> = stateStore.getState(FocusSessionKey)

    fun pauseFocus(nowMillis: Long) {
        stateStore.updateState(FocusSessionKey) { current ->
            if (current is FocusSession.Running) {
                FocusSession.Paused(
                    taskId = current.taskId,
                    remainingMillis = (current.endTimeMillis - nowMillis)
                        .coerceAtLeast(0L),
                )
            } else {
                current
            }
        }
    }

    fun resumeFocus(nowMillis: Long) {
        stateStore.updateState(FocusSessionKey) { current ->
            if (current is FocusSession.Paused) {
                FocusSession.Running(
                    taskId = current.taskId,
                    endTimeMillis = nowMillis + current.remainingMillis,
                )
            } else {
                current
            }
        }
    }
}

集成

StateStore 与 Jetpack DataStore 集成,可在进程终止和应用重启后持久保存和恢复状态。

DataStore

借助 androidx.appstate.statestore:statestore-datastore 工件,您可以在共享 StateStore 实例上配置一次持久性,这样任何管理器的持久化密钥都会自动使用 DataStore 从磁盘保存和恢复。

androidx.appstate.datastore 软件包提供以下 API:

package androidx.appstate.datastore

import androidx.appstate.statestore.StateStore
import androidx.datastore.core.DataStore
import kotlinx.coroutines.CoroutineScope

public interface PersistToDataStore

public suspend fun StateStore.syncToDataStore(
    path: String,
    coroutineScope: CoroutineScope,
)

public suspend fun StateStore.syncToDataStore(
    dataStore: DataStore<StateStorePreferences>,
)
  1. 实现 PersistToDataStore:通过实现 PersistToDataStore 接口,标记应保留的所有 StateStoreKey:

@Serializable
private data object FocusSessionKey :
    StateStoreKey<FocusSession>(defaultValue = FocusSession.Idle),
    PersistToDataStore

  1. 将 StateStore 绑定到 DataStore:在设置共享 StateStore 实例时(例如,在 Application 类或依赖项注入模块中),调用 syncToDataStore 一次。由于 syncToDataStore 是一个 suspend 函数,它会无限期挂起,以从磁盘恢复已保存的值,并继续在提供的 coroutineScope 中保存未来的更改,因此请在 launch 内调用它,并传递应用级 CoroutineScope。启动时,getState 最初会在内存中返回 defaultValue,并在 DataStore 完成从磁盘读取后更新 State:

class ProductivityApplication : Application() {
    val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

    val stateStore = StateStore().also { store ->
        // Restore saved values and automatically persist future changes
        // for all PersistToDataStore keys across managers
        applicationScope.launch {
            store.syncToDataStore(
                path = dataStoreFile("statestore.json").absolutePath,
                coroutineScope = applicationScope,
            )
        }
    }

    val focusSessionManager = FocusSessionManager(stateStore)
}

DataStore 持久化指南

由于每当 PersistToDataStore 键的值发生更改时,syncToDataStore 都会序列化并写入磁盘,因此在选择要持久保存的内容和时间时,请遵循以下准则:

  • 保存开始或结束时间戳,而不是每个计时器滴答:对于计时器或播放进度,请持久保存稳定的参考值(例如 endTimeMillis 或 startedAtMillis),而不是每秒或每帧更新一次 PersistToDataStore 键,并在界面中计算实时经过或剩余的显示时间。
  • 在更新持久化键之前,对快速用户输入进行去抖动处理:对于文本草稿、搜索查询或滑块,请将即时输入保存在内存中的 MutableState(或未实现 PersistToDataStore 的 StateStoreKey)中,并且仅在输入稳定后对持久化 StateStoreKey 调用 setState。
  • 使持久化键保持轻量:每次更新时,StateStore 中的所有 PersistToDataStore 键都会一起序列化到单个文件中。持久保存紧凑的会话状态(例如活跃 ID、过滤条件选择或正在填写的表单草稿),并将大型集合或可查询的数据集保留在 Room 中。