第六篇:状态管理——从 mutableStateOf 到 StateFlow

6.1 为什么状态管理是 Compose 的核心

Compose 的核心公式:

ini 复制代码
UI = f(State)

UI 是状态的函数------当状态变化,UI 自动更新。这意味着在 Compose 中,状态管理的质量直接决定应用的可靠性、性能和可维护性

但什么是"状态"?

状态:任何可能随时间变化的值。比如:用户的输入文本、网络请求的加载状态、列表中的数据项、开关的选中状态。

6.2 mutableStateOf ------ 最基础的状态容器

kotlin 复制代码
@Composable
fun Counter() {
    // count 是一个 MutableState<Int> 对象
    val count = remember { mutableStateOf(0) }
    
    Column {
        Text("Count: ${count.value}")   // 读取 count.value
        Button(onClick = { count.value++ }) {  // 修改 count.value
            Text("Increment")
        }
    }
}

等价的 Kotlin 属性委托写法

kotlin 复制代码
// 通过 by 委托,省略 .value
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

by= 的区别var count by mutableStateOf(0) 使用 Kotlin 属性委托,每次访问自动委托给状态的 getter/setter。推荐使用 by 写法,代码更简洁。

mutableIntStateOf ------ 原始类型优化

csharp 复制代码
// 用 mutableIntStateOf 替代 mutableStateOf<Int>,避免装箱
var count by remember { mutableIntStateOf(0) }

// 同理
var flag by remember { mutableBooleanStateOf(false) }
var value by remember { mutableFloatStateOf(0f) }

对于频繁更新的数值类型,使用原始类型变体能减少装箱拆箱开销。

6.3 remember ------ 记忆化的关键

remember 的作用:在重组中保持值不被重置

kotlin 复制代码
@Composable
fun WithoutRemember() {
    // 每次重组都会创建新的 state,从 0 开始
    var count = mutableStateOf(0)  
    Text("$count")  // 点按钮,count 变 1,触发了重组,但又变回 0
    
    Button(onClick = { count.value++ }) {
        Text("Click")
    }
}

@Composable
fun WithRemember() {
    // remember 在首次组合时创建,后续重组跳过
    var count by remember { mutableStateOf(0) }
    Text("$count")  // 正常累加
    
    Button(onClick = { count++ }) {
        Text("Click")
    }
}

remember 的依赖键

kotlin 复制代码
// 传入 keys 参数 ------ 当 keys 变化时重新计算
@Composable
fun Greeting(userId: String) {
    val user = remember(userId) {  // userId 变化时重新请求
        viewModel.fetchUser(userId)
    }
    Text("Hello, ${user.name}")
}

rememberSaveable ------ 跨进程保存

scss 复制代码
// 记住状态,且能在 Activity 被销毁重建后恢复(如旋转屏幕)
var text by rememberSaveable { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it }
)

rememberSaveable 通过 Bundle 保存状态,默认支持基本类型和 Serializable/Parcelable,自定义类型需要自定义 Saver

6.4 State 的观察与生命周期

kotlin 复制代码
// State 对象的生命周期
@Composable
fun Parent() {
    var show by remember { mutableStateOf(true) }
    
    if (show) {
        Child()  // Child 被加入组合树
    } else {
        // Child 被移除组合树
        // Child 内的所有 remember 状态也会被自动清理
    }
    
    Button(onClick = { show = !show }) {
        Text("Toggle")
    }
}

@Composable
fun Child() {
    // 这个状态的生命周期与 Child 的组合生命周期绑定
    val localState = remember { mutableStateOf("local") }
    Text(localState.value)
}

关键理解remember 存储的状态在 Composable 函数离开组合树(Disposal) 时被自动清理。不需要像 View 系统中手动释放资源。

6.5 mutableStateListOf / mutableStateMapOf

对于列表和 Map 类型的变更,普通的 mutableStateOf 可能无法精确检测内部元素变化:

csharp 复制代码
// ❌ 不起作用:list 引用没变,只是内部元素变了
var list by remember { mutableStateOf(listOf(1, 2, 3)) }
list.add(4)  // 即使添加了元素,Compose 也不会感知
scss 复制代码
// ✅ 使用 mutableStateListOf
val list = remember { mutableStateListOf(1, 2, 3) }

Button(onClick = { list.add(list.size + 1) }) {  // ✅ 触发重组
    Text("Add: ${list.joinToString()}")
}

Button(onClick = { list[0] = 99 }) {  // ✅ 修改元素也触发重组
    Text("Modify first")
}

mutableStateListOf 创建的 SnapshotStateList 在增删改元素时都会触发重组,而不需要替换整个 List 引用。

同样的还有 mutableStateMapOf

arduino 复制代码
val map = remember { mutableStateMapOf<String, Int>() }
map["key"] = 1   // ✅ 触发重组
map.remove("key") // ✅ 触发重组

6.6 StateFlow / Flow 集成

在真实项目中,状态通常来自 ViewModel 和 Repository 层,而非直接定义在 Composable 中。

kotlin 复制代码
// ViewModel 层
class MainViewModel : ViewModel() {
    // 方式一:StateFlow
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    // 方式二:Compose 的 mutableStateOf(不推荐在 ViewModel 中使用)
    var composeState by mutableStateOf(UiState())
        private set
}

// Composable 层
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
    // 方式一:collectAsState() ------ 将 StateFlow 转为 Compose State
    val uiState by viewModel.uiState.collectAsState()
    
    // 方式二:collectAsStateWithLifecycle()(推荐------感知生命周期)
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    // 使用
    when (uiState) {
        is Loading -> LoadingIndicator()
        is Success -> Content(uiState.data)
        is Error -> ErrorView(uiState.message)
    }
}

collectAsStateWithLifecycle ------ 推荐的做法

scss 复制代码
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.0")
kotlin 复制代码
@Composable
fun MainScreen() {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // ↑ 当 Lifecycle 低于 CREATED 时停止收集,回到 RESUMED 时继续
    // 防止后台状态持续更新 UI,浪费资源
}

何时用哪个 ?如果状态来自 ViewModel,优先用 collectAsStateWithLifecycle;如果是 Composable 内部的临时状态,用 remember + mutableStateOf

6.7 produceState ------ 将非 Compose 状态转为 Compose State

kotlin 复制代码
@Composable
fun ImageLoader(
    url: String,
    modifier: Modifier = Modifier
) {
    // 将协程(网络请求/数据库)转换为 Compose State
    val bitmap by produceState<Bitmap?>(initialValue = null, url) {
        val image = loadImage(url)  // suspend 函数
        value = image
    }
    
    if (bitmap != null) {
        Image(bitmap = bitmap, contentDescription = null, modifier = modifier)
    } else {
        CircularProgressIndicator(modifier = modifier)
    }
}

produceState 的等价实现(理解原理):

kotlin 复制代码
@Composable
fun <T> produceState(
    initialValue: T,
    key: Any?,
    producer: suspend CoroutineScope.() -> T
): State<T> {
    val result = remember { mutableStateOf(initialValue) }
    
    LaunchedEffect(key) {
        result.value = producer()
    }
    
    return result
}

6.8 本章练习

  1. 实现一个 TodoInput 组件:包含一个 TextField 和一个 Button,点击 Button 将输入文本添加到列表中
  2. 优化练习1:使用 rememberSaveable 让输入框内容在屏幕旋转后不丢失
  3. 创建一个 ViewModel,暴露 StateFlow<UiState>,在 Composable 中用 collectAsStateWithLifecycle() 收集
  4. mutableStateListOf 实现一个可增删的 Todo List
scss 复制代码
// 💡 练习 4 参考
@Composable
fun TodoList() {
    val items = remember { mutableStateListOf<String>() }
    var input by remember { mutableStateOf("") }

    Column(modifier = Modifier.padding(16.dp)) {
        Row {
            TextField(
                value = input,
                onValueChange = { input = it },
                modifier = Modifier.weight(1f)
            )
            Button(
                onClick = {
                    if (input.isNotBlank()) {
                        items.add(input)
                        input = ""
                    }
                }
            ) { Text("Add") }
        }

        LazyColumn {
            itemsIndexed(items) { index, item ->
                Row {
                    Text(item, modifier = Modifier.weight(1f))
                    IconButton(onClick = { items.removeAt(index) }) {
                        Icon(Icons.Default.Delete, "Delete")
                    }
                }
            }
        }
    }
}

6.9 本章小结

概念 作用 生命周期
mutableStateOf 基础状态容器 remember 控制
remember 在重组中保持值 Composable 在组合中时
rememberSaveable 跨进程/旋转保存状态 通过 Bundle 持久化
mutableStateListOf 可观测的列表 同 remember
StateFlow 异步数据流 collectAsState 控制
collectAsStateWithLifecycle 生命周期感知的 StateFlow 收集 跟随 Lifecycle
produceState 将异步数据转为 State 受 key 和组合生命周期控制

下一篇:状态提升与单向数据流------架构设计的核心模式。

相关推荐
pyz6662 小时前
Retrofit 源码分析
android·retrofit
xiaoduzi19912 小时前
Android 线程池总结
android
YIN_尹2 小时前
【Linux系统编程】基础IO第二讲——文件描述符
android·linux·服务器
朝星2 小时前
Android开发[10]:性能优化之内存
android·kotlin
像风一样自由20203 小时前
量化压缩实战:INT8 / INT4 / AWQ / GPTQ 全面对比
android·人工智能·语言模型·大模型
brycegao3213 小时前
Android MVI进阶:纯原生实现Slot化可插拔架构
android·kotlin·架构设计·mvi·viewmodel
2601_961194023 小时前
27考研资料|百度网盘|夸克网盘
android·xml·考研·ios·iphone·xcode·webview
故渊at3 小时前
第二板块:Android 四大组件标准化学理 | 第十篇:ContentProvider 数据共享与 SQLite 引擎
android·jvm·数据库·sqlite·contentprovider
Kapaseker3 小时前
你遇到过 Kotlin 协程中的竞争问题吗?
android·kotlin