Kotlin Compose之mutableSateOf函数详解

898 阅读7分钟

mutableStateOf是Jetpack Compose中用于创建可观察状态的函数。它返回一个MutableState对象,这个对象可以被Compose运行时观察。当MutableState的值发生变化时,Compose会自动重组使用该状态的可组合函数。

一、结构及原理

1. 核心组成
  • State 对象:存储可变状态
  • 观察者模式:监听状态变化
  • 重组系统:触发 UI 更新
  • 线程安全机制:确保状态一致性
graph TD
    A[MutableStateOf] --> B[State 对象]
    A --> C[观察者模式]
    A --> D[重组系统]
    A --> E[线程安全机制]
    B --> F[存储可变状态]
    C --> G[监听状态变化]
    D --> H[触发 UI 更新]
    E --> I[确保状态一致性]
2. 工作原理
a. 创建状态:

当调用mutableStateOf时,它会创建一个MutableState对象。这个对象内部包含了实际的值和一些额外的元数据。

b. 注册观察者:

当一个可组合函数使用这个状态时,Compose运行时会自动将该可组合函数注册为这个状态的观察者。状态观察者的注册过程如下:

  • 当可组合函数首次执行时,Compose会创建一个"Composition"对象。
  • Composition对象包含了UI树的结构和每个节点使用的状态信息。
  • 在可组合函数执行过程中,每次读取MutableState的值时,Compose都会记录这个状态被当前节点使用。
  • 这种记录建立了状态和使用它的UI组件之间的依赖关系
c. 更新状态:

当你修改MutableState的值时(通过value属性或setValue函数),它会标记自己为"已更改",状态更改的标记:

  • 当MutableState的值被修改时,Compose使用一种称为"快照系统"(Snapshot system)的机制来跟踪变化:
    • 每次状态更改都会创建一个新的"快照"。
    • 快照系统使用一个版本计数器来跟踪更改。
    • 当状态被修改时,它会增加自己的版本号。
    • Compose运行时会比较状态的当前版本号与上次读取时的版本号,以确定状态是否发生了变化
d. 触发重组:

在下一个组合周期,Compose运行时会检查所有被标记为"已更改"的状态,并重新执行使用了这些状态的可组合函数。重组的触发和执行遵循以下流程:

  • 状态变化检测:
    • Compose运行时定期检查所有被跟踪的状态。
    • 如果发现状态的版本号发生变化,就会标记相关的UI组件需要重组。
  • 调度重组:
    • Compose使用一个重组调度器(Recomposer)来管理重组任务。
    • 被标记为需要重组的组件会被添加到重组队列中。
  • 执行重组:
    • 在下一个组合周期,Recomposer会执行队列中的重组任务。
    • 重组从需要更新的最顶层组件开始,然后向下传播
    • Compose会尝试重用之前的组合结果,只更新必要的部分。如果重组结果与之前相同,Compose可能会跳过子树的重组。
e. 重置状态:

在重组完成后,状态会被重置为"未更改"。

d. 状态时序图
sequenceDiagram
    participant Developer as 开发者
    participant Compose as Compose框架
    participant State as MutableStateImpl
    participant Snapshot as SnapshotStateObserver

    Developer->>State: 初始化 mutableStateOf(0)
    State->>Snapshot: 注册状态观察者
    Developer->>Compose: 在组合函数中读取状态值
    Compose->>State: 记录依赖关系
    State->>Snapshot: 添加组合函数到依赖列表
    Developer->>State: 更新状态值
    State->>Snapshot: 通知状态变化
    Snapshot->>Compose: 标记需要重新组合
    Compose->>Developer: 重新组合相关函数
    Developer->>Compose: 更新界面

二、SnapshotState 工作原理:

在 Jetpack Compose 中,SnapshotState 是用来管理状态并确保状态变化时 UI 能够自动更新的机制。它通过快照(Snapshot)、读观察者(ReadObserver)和写观察者(WriteObserver)来实现状态的追踪和更新。

1. 主要概念或者接口包括:
  • Snapshot:一个快照表示一个状态的静态视图。当读操作发生时,状态的值来自于一个快照。
  • StateRecord:每个状态对象都维护一个 StateRecord 列表来追踪状态的变化。
  • ReadObserver:记录哪些状态在当前组合中被读取。
  • WriteObserver:记录哪些状态在当前组合中被写入,并通知相关的读操作进行重组。
2. 作用
  1. 响应式编程: 使得UI可以根据状态变化自动更新。
  2. 高效更新: 通过观察者模式,只有状态变化的部分UI会重新组合(recompose),提高性能。
  3. 线程安全: 通过快照机制保证状态在多线程环境下的安全性。
3. 工作流程

读取状态:

  • UI 组件读取状态时,SnapshotState 获取当前的快照。
  • 快照触发 ReadObserver,记录当前组合中读取的状态。
  • 状态值从 StateRecord 中获取,并返回给 UI 组件。

写入状态:

  • 当状态被写入时,SnapshotState 触发 WriteObserver
  • 写观察者通知相关的读观察者,标记需要重组的范围。
  • 读观察者收到通知后,快照标记需要重组的范围,并触发重新组合过程。
  • UI 组件更新 StateRecord 中的状态值,并确认状态更新。
4. SnapshotState如何确保一致性的机制?
  • 快照机制:读操作发生时,状态的值来自于一个快照,这确保了在重组过程中看到的是一致的状态。

    • 快照隔离:当开始重组时,Compose创建一个新的快照。所有的读操作都是针对这个快照进行的,确保在整个重组过程中看到的是一致的状态视图。
    • 写入缓冲:在重组过程中的写操作不会立即影响当前快照,相反,它们被缓冲到一个新的快照中。
  • 状态记录:每个状态对象都有一个 StateRecord 列表,记录状态的历史变化。读取操作总是针对最新的快照,写入操作更新状态记录并通知观察者。

  • 观察者模式ReadObserver 和 WriteObserver 确保了状态变化时相关的 UI 组件能够自动重组,保持状态和 UI 的一致性。

  • 原子性提交:重组完成后,所有的变更会被原子性地提交。这确保了状态更新的一致性,避免了中间状态。如果在重组过程中检测到并发修改,Compose可能会重新开始重组过程。

5. 判断状态是否改变?
  1. StateRecord:每个状态对象都有一个 StateRecord 列表,每个记录都有一个版本号。
  2. Versioning:每当状态发生变化时,StateRecord 的版本号会更新。
  3. Comparison:在读写操作时,通过比较当前版本号和记录中的版本号来判断状态是否发生变化。
7. SnapshotState时序图
sequenceDiagram
    participant UI
    participant SnapshotState
    participant Snapshot
    participant StateRecord
    participant ReadObserver
    participant WriteObserver

    UI ->> SnapshotState: 读取状态
    SnapshotState ->> StateRecord: 获取状态值和版本号
    StateRecord -->> SnapshotState: 返回值和版本号
    SnapshotState ->> Snapshot: 记录读操作和版本号
    Snapshot -->> ReadObserver: 触发读观察者
    ReadObserver -->> UI: 返回状态值

    UI ->> SnapshotState: 写入状态
    SnapshotState ->> WriteObserver: 触发写观察者
    WriteObserver ->> StateRecord: 更新状态值和版本号
    StateRecord -->> WriteObserver: 确认状态更新
    WriteObserver ->> Snapshot: 通知读观察者
    Snapshot ->> ReadObserver: 判断版本号是否变化
    ReadObserver ->> UI: 标记需要重组


三、结合UI点几更新流程
@Composable fun Counter() { 
    var count by remember { mutableStateOf(0) } 
    Column { 
        Button(onClick = { count++ }) {
            Text("Clicked $count times")
        } 
        Text("Current count: $count", modifier = Modifier.padding(top = 16.dp)) 
    }
} 

初始化和读取阶段

  1. 初始化组合

    • UI 开始初始化组合,启动整个过程。
  2. 调用 Composable 函数

    • 组合系统调用 Composable 函数,以生成 UI 结构。
  3. 读取状态

    • 在 Composable 函数中读取状态 (如 count.value)。
    • SnapshotState 请求当前快照以获取状态值。
    • 快照系统从 StateRecord 获取并返回状态值。
    • SnapshotState 触发读观察者 (ReadObserver),记录读取操作。
    • Composable 函数返回生成的 UI 结构,组合系统将其渲染到 UI 上。

Composable 执行完成阶段

  1. 执行完成

    • UI 通知组合系统,Composable 函数执行完成。

状态更新阶段

  1. 用户点击按钮

    • 用户在 UI 上点击按钮,触发状态更新操作 (如 count.value += 1)。
  2. 写入新状态

    • Composable 函数写入新的状态值。
    • SnapshotState 将新状态值写入 StateRecord,并更新版本号。
    • SnapshotState 触发写观察者 (WriteObserver),通知状态变化。
    • WriteObserver 通知重组器 (Recomposer) 需要进行重组。

重组阶段

  1. 标记重组

    • 重组器标记需要重组的 UI 范围。
  2. 重新调用 Composable 函数

    • 重组器重新调用相关的 Composable 函数。
  3. 读取新状态值

    • 在 Composable 函数中读取新的状态值 (如 count.value)。
    • SnapshotState 请求新的快照以获取更新后的状态值。
    • 快照系统从 StateRecord 获取并返回新的状态值。
    • SnapshotState 触发读观察者 (ReadObserver),记录读取操作。
    • Composable 函数返回更新后的 UI 结构,组合系统将其更新到 UI 上。

通过以上步骤,Kotlin Compose 能够在状态变化时自动管理和更新 UI,确保高效且一致的用户界面响应。

sequenceDiagram
    participant UI
    participant Composition
    participant ComposableFunction
    participant SnapshotState
    participant StateRecord
    participant Snapshot
    participant ReadObserver
    participant WriteObserver
    participant Recomposer

    %% 初始化和读取阶段
    Note over UI, Recomposer: 初始化和读取阶段
    UI ->> Composition: 初始化组合
    Composition ->> ComposableFunction: 调用 Composable 函数
    ComposableFunction ->> SnapshotState: 读取状态 (count.value)
    SnapshotState ->> Snapshot: 获取快照
    Snapshot ->> StateRecord: 返回状态值
    StateRecord -->> SnapshotState: 返回状态值
    SnapshotState ->> ReadObserver: 触发读观察者
    ReadObserver -->> ComposableFunction: 记录读操作
    ComposableFunction ->> Composition: 返回 UI 结构
    Composition ->> UI: 渲染 UI 结构

    %% Composable 执行完成阶段
    Note over UI, Recomposer: Composable 执行完成阶段
    UI ->> Composition: Composable 执行完成

    %% 状态更新阶段
    Note over UI, Recomposer: 状态更新阶段
    UI ->> ComposableFunction: 用户点击按钮 (count.value += 1)
    ComposableFunction ->> SnapshotState: 写入新状态 (count.value += 1)
    SnapshotState ->> StateRecord: 更新状态值
    StateRecord ->> SnapshotState: 更新版本号
    SnapshotState ->> WriteObserver: 触发写观察者
    WriteObserver ->> Recomposer: 通知重组

    %% 重组阶段
    Note over UI, Recomposer: 重组阶段
    Recomposer ->> Composition: 标记需要重组的范围
    Recomposer ->> ComposableFunction: 重新调用 Composable 函数
    ComposableFunction ->> SnapshotState: 读取新状态值 (count.value)
    SnapshotState ->> Snapshot: 获取新快照
    Snapshot ->> StateRecord: 返回新状态值
    StateRecord -->> SnapshotState: 返回新状态值
    SnapshotState ->> ReadObserver: 触发读观察者
    ReadObserver -->> ComposableFunction: 记录读操作
    ComposableFunction ->> Composition: 返回更新后的 UI 结构
    Composition ->> UI: 更新 UI 结构