Android Compose SideEffect(副作用)实例加倍详解

前几天的文章,整理了上个月的学习心得。这段时间又仔细研究了下SideEffect,秉着闲着找事干,有事干才不想着去洗脚的原则,我又开始了读书笔记式的码字。源码在文章末尾。各副作用函数特性表也在文章末尾
部分例子在实际开发中是可能存在一定瑕疵的,旨在让大家明白如何使用。使用时请注意

一、副作用到底有哪些?

  • LaunchedEffect
  • DisposableEffect
  • SideEffect
  • rememberCoroutineScope
  • produceState
    副作用函数关联概念(个人觉得不是副函数):
  • derivedStateOf
  • rememberUpdatedState

上篇文章我已经简单介绍了这几个副作用函数,可总觉得太过简略,例子也很简单,完全不贴合实际开发,以后我怎么抄代码嘛。好的,下面我们就从实际出发举例子,让大家更好抄更好理解。

LaunchedEffect

1、概括

LaunchedEffect(key) = 页面进入时自动启动协程,key变了就取消旧的,重启新的。

2、核心机制

机制 说明
自动启动 Composable 进入组合时,自动 launch 协程
key 控制 key 不变 → 协程继续;key 变 → 取消旧协程,启动新协程
自动取消 离开页面 → 自动 cancel()零泄漏

3、实际开发中的场景

  • 场景一:加载网络数据(最常见)
kotlin 复制代码
@Composable
fun FetchUserInfo(userId: Int) {
    var user by remember { mutableStateOf<User?>(null) }
    var loading by remember { mutableStateOf(true) }
    var error by remember { mutableStateOf<String?>(null) }

    LaunchedEffect(userId) {  // key = userId
        loading = true
        error = null
        try {
            val result = withContext(Dispatchers.IO) {
                RetrofitClient.apiService.getUsersById(userId) 
            }
            user = result
        } catch (e: Exception) {
            error = e.message
        } finally {
            loading = false
        }
    }

    when {
        loading -> CircularProgressIndicator()
        error != null -> Text("错误: $error", color = Color.Red)
        user != null -> Column {
            Text("用户名: ${user!!.name}", style = MaterialTheme.typography.bodyLarge)
            Text("邮箱: ${user!!.email}")
        }
    }
}

代码核心:切换 userId → 自动取消旧请求,发起新请求

  • 场景二:一次性任务初始化(日志、埋点、第三方初始化)
kotlin 复制代码
@Composable
fun AnalyticsScreen(screenName: String) {
    LaunchedEffect(Unit) {  // 只执行一次
        //Analytics.uploadPoint(screenName)
        Log.i("当前页面: $screenName")
    }

    Text("当前页面: $screenName")
}

为什么使用Unit?Unit在Kotlin里面是常量,意味着key永远不变 ,所以只在页面首次进入时执行一次。

小知识:LaunchedEffect(true)和LaunchedEffect(Unit)行为一致,因为 true 也是常量。但 true容易让人误解为"每次都执行",建议统一用 Unit。

  • 场景三:轮询(实时获取数据)
kotlin 复制代码
@Composable
fun GoldPrice(symbol: String) {
    var price by remember { mutableStateOf<Price?>(null) }
    LaunchedEffect(symbol) {
        while (true) {
            price = withContext(Dispatchers.IO) {
                GoldPriceRetrofitClient.apiService.getGoldPrice(symbol)
            }
            delay(30_000)
        }
    }
    Card(
        modifier = Modifier.padding(16.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(symbol, style = MaterialTheme.typography.bodyMedium)
            Text("¥${price?.price?:"未知"}", style = MaterialTheme.typography.bodyLarge)
        }
    }
}

代码核心:每30秒获取一次金价。调整商品编码时,自动停止旧商品轮询,开始新轮询。

  • 场景四:执行一个协程
kotlin 复制代码
@Composable
fun LoadData(viewModel: SideEffectsViewModel = viewModel()) {
    var user by remember { mutableStateOf<User?>( null) }
    LaunchedEffect(Unit) {
        user = viewModel.getUserInfo(1)
    }
}

代码核心:执行获取用户查询的supend的方法

4、快速记忆

进入页 → 自动启动 → key变化自动重启 → 离开自动取消

5、常犯错误

错误 后果 正确
LaunchedEffect(true) 易产生语义歧义 Unit
不用 withContext(IO) 阻塞 UI 必须切线程
忘记 key 数据不更新 必须加依赖参数

6、总结

LaunchedEffect 是Compose中页面级异步任务的首选,key+自动取消是其核心。

DisposableEffect

1、概括

DisposableEffect(key) = 「进入时注册,离开时自动清理」的资源管理神器

2、核心机制

机制 说明
进入时执行 Composable 进入组合时,执行 effect
key 控制 key 变化 → onDispose,再重新注册
离开时自动清理 离开页面 → 自动调用 onDispose,防止内存泄漏

3、实际开发中场景

  • 场景一:注册/注销广播接收器(最常见)
kotlin 复制代码
@Composable
fun NetworkChangeListener(context: Context) {
    val connectivityManager = remember { 
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 
    }
    var isConnected by remember { mutableStateOf(true) }

    DisposableEffect(Unit) {
        val callback = object : ConnectivityManager.NetworkCallback() {
            override fun onAvailable(network: Network) {
                isConnected = true
            }
            override fun onLost(network: Network) {
                isConnected = false
            }
        }

        connectivityManager.registerDefaultNetworkCallback(callback)

        onDispose {
            connectivityManager.unregisterNetworkCallback(callback) // 必须清理!
        }
    }

    Card(
        colors = CardDefaults.cardColors(
            containerColor = if (isConnected) Color.Green else Color.Red
        )
    ) {
        Text(
            text = if (isConnected) "在线" else "离线",
            color = Color.White,
            modifier = Modifier.padding(8.dp)
        )
    }
}

代码核心:监听设备网络状态,断开网络显示离线,否则显示在线。

  • 场景二:添加移除传感器
kotlin 复制代码
@Composable
fun StepCounter(sensorManager: SensorManager) {
    var steps by remember { mutableStateOf(0) }

    DisposableEffect(Unit) {
        val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
        val listener = object : SensorEventListener {
            override fun onSensorChanged(event: SensorEvent) {
                steps = event.values[0].toInt()
            }
            override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
        }

        sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)

        onDispose {
            sensorManager.unregisterListener(listener)
        }
    }

    Text("今日步数: $steps 步", style = MaterialTheme.typography.headlineMedium)
}

代码核心:实时监测运动传感器,统计步数。

  • 场景三:订阅取消Flow(ViewModel 搭配使用)
kotlin 复制代码
@Composable
fun FlowSubscription(viewModel: SideEffectsViewModel = viewModel()) {
    var message by remember { mutableStateOf("等待消息...") }

    DisposableEffect(Unit) {
        val job = viewModel.messageFlow
            .onEach { message = it }
            .launchIn(viewModel.viewModelScope)

        onDispose {
            job.cancel() // 取消订阅
        }
    }
    Column(modifier = Modifier.fillMaxSize()) {
        Button(onClick = {
            viewModel.sendMessage()
        }, modifier = Modifier.padding(16.dp)) {
            Text("点击发消息")
        }

        Text(message)
    }
}

代码核心:离开时,自动取消Flow

  • 场景四:key变化时,重置资源(如切换用户)
kotlin 复制代码
@Composable
fun UserSessionTracker(userId: String) {
    DisposableEffect(userId) {
        LoginManager.login(userId)
        println("用户 $userId 已登录")

        onDispose {
            LoginManager.logout()
            println("用户已登出")
        }
    }

    Text("当前用户: $userId")
}

代码核心:切换 userId → 自动登出旧用户,登录新用户

4、快速记忆

进入注册 → key变化重置 → 离开自动清理 → 安全无泄漏

5、常见错误

错误 后果 正确
忘记 onDispose 内存泄漏 必须清理
LaunchedEffect 注册监听 无法手动清理 DisposableEffect
key 写错 重复注册 用稳定 key

6、总结

DisposableEffect 是 Compose 中「资源生命周期管理」的唯一正确方式, 掌握 onDispose + key,解决内存泄漏的问题

SideEffect

1、一句话概括

SideEffect = 「每次重组都执行一次」的轻量副作用,不自动取消,适合日志、埋点、同步非 Compose 状态

2、核心机制

机制 说明
每次重组都执行 只要 UI 重组,SideEffect 就运行一次
无 key 控制 不支持 key不能重启或取消
不自动清理 离开页面不会自动取消,需手动控制

3、实际开发中的四大场景

  • 场景一:页面曝光埋点(常见)
kotlin 复制代码
@Composable
fun AnalyticsScreen(screenName: String) {
    SideEffect {
        Analytics.logScreenView(screenName)  // 每次重组都上报
        Log.i("compose 曝光","曝光: $screenName")
    }

    Text("当前页面: $screenName")
}
  • 场景二:日志输出及调试
kotlin 复制代码
@Composable
fun DebugRecomposition() {
    var count by remember { mutableStateOf(0) }

    SideEffect {
        Log.d("compose日志","重组发生了!count = $count")
    }

    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) { Text("加1") }
    }
}
  • 场景三:同步或发送数据到外部(如SharedPreferences、EventBus、ViewModel)
kotlin 复制代码
@Composable
fun SyncTheme(isDark: Boolean) {
    val prefs = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
    SideEffect {
        prefs.edit().putBoolean("dark_mode", isDark).apply()
        Log.d("compose 日志", "主题已保存: ${if (isDark) "深色" else "浅色"}")
        EventBus.post(isDark)
    }

    // UI...
}
kotlin 复制代码
@Composable
fun BrightnessControl(viewModel: SideEffectsViewModel = viewModel()) {
    var brightness by remember { mutableFloatStateOf(0.5f) }

    // 每次 brightness 改变并重组完成后,同步到 ViewModel
    SideEffect {
        viewModel.currentBrightness = brightness
    }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text("当前亮度: ${(brightness * 100).toInt()}%")
        Slider(value = brightness, onValueChange = { brightness = it })
    }
}

核心:每次切换主题 → 自动保存到本地,并通知其他页面

  • 场景四:与非 Compose 组件通信(如 TextureView)
kotlin 复制代码
@Composable
fun CameraPreview(textureView: TextureView) {
    SideEffect {
        CameraManager.bind(textureView.surfaceTexture)
    }

    AndroidView(factory = { textureView })
}

4、快速记忆

重组就跑,轻量选它,日志埋点,首选SideEffect。

5、常见错误

错误 后果 正确
放网络请求 每次重组都请求 LaunchedEffect
放耗时操作(非轻量) 卡 UI withContext(IO) + LaunchedEffect
期望只执行一次 不会 LaunchedEffect(Unit)

总结:每次重组都会执行。注意:SideEffect 是在 重组完成后立即执行 ,没有协程。而 LaunchedEffect 是在进入组合时启动协程。

rememberCoroutineScope

1、一句话概括:

rememberCoroutineScope() = 「手动、可控、生命周期安全 」的协程作用域,重组不重启,离开页面自动取消

2、核心机制

机制 说明
手动启动 必须 scope.launch { } 才执行
重组不影响 重组时 同一个 scope,不会重新创建
自动取消 离开页面 → 自动 cancel() ,所有协程停止

3、常用场景

  • 场景一:异步操作(如文件保存,最常见)
kotlin 复制代码
@Composable
fun SaveButton(viewModel: SaveViewModel) {
    val scope = rememberCoroutineScope()
    var saving by remember { mutableStateOf(false) }

    Button(
        enabled = !saving,
        onClick = {
            saving = true
            scope.launch {
                try {
                    viewModel.saveData()  // 模拟文件保存
                } finally {
                    saving = false  // 发送错误时,重置UI
                }
            }
        }
    ) {
        Text(if (saving) "保存中..." else "保存")
    }
}

场景二:定时任务

kotlin 复制代码
@Composable
fun TimerWithCoroutineScope() {
    val scope = rememberCoroutineScope()
    var count by remember { mutableIntStateOf(0) }
    
    Button(onClick = {
        scope.launch {
            repeat(10) {
                delay(1000)
                count++
            }
        }
    }) {
        Text("开始定时任务")
    }
    
    Text("计数: $count")
}

场景三:批量处理

kotlin 复制代码
@Composable
fun BatchProcess(items: List<String>) {
    val scope = rememberCoroutineScope()
    var progress by remember { mutableStateOf(0) }

    Button(onClick = {
        scope.launch {
            items.forEachIndexed { index, item ->
                processItem(item)
                progress = index + 1
            }
        }
    }) {
        Text("处理 ${items.size} 项")
    }
    LinearProgressIndicator(progress = progress / items.size.toFloat())
}
  • 场景四:与Snackbar搭配(Material3 常见用法)
kotlin 复制代码
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackbarExample() {
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
        floatingActionButton = {
            FloatingActionButton(onClick = {
                scope.launch {
                    snackbarHostState.showSnackbar("Hello Snackbar!")
                }
            }) {
                Text("点我")
            }
        }
    ) { padding ->
        Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
            Text("点击右下角按钮显示 Snackbar")
        }
    }
}

4、快速记忆

rememberCoroutineScope → 手动 launch → 自动取消 → 重组不重启

它与 LaunchedEffect 最大的区别是:需要事件触发 而不是自动执行。

5、常见错误

错误 后果 正确
LaunchedEffect 放点击 每次重组都弹 scope.launch
不用 remember 每次重组新 scope 必须 rememberCoroutineScope()
GlobalScope 无法取消 禁止使用!

6、总结

rememberCoroutineScope 是 Compose 中「用户交互 → 异步操作」的最佳实践,需要手动启动协程,首选选他。

ProduceState

1、一句话概括

produceState = 「外部数据源 转化为 Compose State」的桥梁,自动协程 + 自动取消 + 支持 key

2、核心机制

机制 说明
协程生产 State 在协程中 value = xxx → 自动触发 UI 重组
自动生命周期 进入 → 启动协程;离开 → 自动取消
key 控制重启 key 变 → 取消旧协程,启动新协程
其更像是collectAsState()的增强版本,支持任意数据源,而collectAsState则只能来源于Flow。

常用场景

  • 场景一:请求网络数据(最常见)
kotlin 复制代码
@Composable
fun UserInfo(userId: Int) {
    val user by produceState<User?>(initialValue = null, userId) {
        value = withContext(Dispatchers.IO) {
            RetrofitClient.apiService.getUsersById(userId)  //根据userId获取用户数据
        }
    }

    when (user) {
        null -> CircularProgressIndicator()
        else -> Text("欢迎,${user!!.name}")
    }
}
  • 场景二:Flow -> State(较常见),替代collectAsState
kotlin 复制代码
class SideEffectsViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val countFlow = _count.asStateFlow()
    fun increment() = _count.value++
}

@Composable
fun FlowCounter(viewModel: SideEffectsViewModel = viewModel()) {
    //原写法
    // val count by viewModel.collectAsState()
    val count by produceState(initialValue = 0) {
        viewModel.countFlow.collect { value = it }
    }

    Text("Count: $count")
}
  • 场景三:回调函数-> State
kotlin 复制代码
@Composable
fun LocationNow(context: Context, locationManager: LocationManager) {
    val location by produceState<String?>(initialValue = null) {
        val listener = LocationListener { location -> value = "经度: ${location.longitude}, 纬度: ${location.latitude}" }
        //权限检查代码省略
        locationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER,
            2000L,
            1f,
            listener)

        awaitDispose {
            locationManager.removeUpdates(listener)
        }
    }
    
    if (location == null) {
        Text("正在获取位置...")
    } else {
        Text("location")
    }
}
  • 场景四:状态处理
kotlin 复制代码
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
}

@Composable
fun <T> loadData(
    key: Any?,
    block: suspend () -> T
): State<Result<T>> = produceState<Result<T>>(Result.Loading, key) {
    try {
        value = Result.Success(block())
    } catch (e: Exception) {
        value = Result.Error(e.message ?: "未知错误")
    }
}

4、快速记忆

生产 State → 协程运行 → 自动取消 → key 控制重启

5、常见错误

错误 后果 正确
不用 withContext(IO) 阻塞 UI 必须切线程
忘记 key 数据不更新 加依赖参数
返回 State 编译错误 必须 by produceState

6、相似对比

项目 produceState collectAsState()
数据源 任意 仅 Flow
支持 key Yes No
自动取消 Yes Yes

7.总结:

produceState 是 Compose 中【外部数据源 -> State】的最好桥梁, 熟悉它,你就能实现任意数据源驱动UI变化了。

derivedStateOf

有人把这个归于副作用函数之类,但我个人觉得不是。原因如下:

  • 纯函数计算:基于其他状态计算的派送状态,不产生任何外部影响
  • 无外部交互:不执行I/O操作,不启动异步任务、不修改外部状态
  • 不直接参与UI更新:虽然在 Composable 中触发,但只是影响 UI 的"外部世界"

1、一句话概括:

derivedStateOf { } = 「只有当结果真正变化时才触发重组」,避免重复计算 + 减少 UI 闪烁

2、为什么需要derivedStateOf?

问题:频繁重组导致性能拉胯

kotlin 复制代码
@Composable
fun UserFilter(nameItems: List<String>, query: String) {
    val filtered = nameItems.filter { it.contains(query) }  // 每次重组都过滤!

    LazyColumn {
        nameItems(filtered) { Text(it) }
    }
}

这段代码你会发现query 变 → 重组 → 每次都重新 filter 。 即使结果没变,也会触发 LazyColumn 重绘 → 性能差 + 闪烁

相关改进参考场景一

3、常用场景

  • 场景一:搜索过滤
kotlin 复制代码
@Composable
fun SearchStr(nameItems: List<String>, query: String) {
    // 使用 derivedStateOf 只在 query 或 nameItems 变化时重新计算
    var searchQuery by remember { mutableStateOf(query) }
    val filteredItems by derivedStateOf {
        nameItems.filter { it.contains(searchQuery, ignoreCase = true) }
    }
    Column {
        TextField(value = searchQuery,
            onValueChange = { searchQuery = it },
            label = { Text("搜索") })
        LazyColumn {
            items(filteredItems) { item ->
                Text(item)
            }
        }
    }
}

代码核心:快速输入时 → 只有结果变了才更新列表,降低了重组次数

  • 场景二:分页加载判断
kotlin 复制代码
@Composable
fun InfiniteList(items: List<String>, onLoadMore: () -> Unit) {
    val listState = rememberLazyListState()
    
    // 只有到达底部时才触发
    val shouldLoadMore by derivedStateOf {
        val lastVisible = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
        lastVisible >= items.size - 2
    }

    LaunchedEffect(shouldLoadMore) {
        if (shouldLoadMore) onLoadMore()
    }

    LazyColumn(state = listState) {
        items(items) { Text(it) }
    }
}

代码核心:计算是否距离底部还有两个Item,是的话触发加载下一页回调操作。

  • 场景三:关联数据或状态实时计算(常见用途)
kotlin 复制代码
@Composable
fun  SumAndAvg(numbers: List<Int>) {
    val sum by remember {
        derivedStateOf {
            numbers.sum()
        }
    }
    val avg by remember {
        derivedStateOf {
            numbers.average()
        }
    }

    Card {
        Column(modifier = Modifier.padding(16.dp)) {
            Text("总和: $sum")
            Text("平均: %.2f".format(avg))
            Text("Numbers 总数: ${numbers.size}")
        }
    }
}

-场景四:控件显示状态判断 (常见)

kotlin 复制代码
@Composable
fun TopButtonShow(listState: LazyListState) {
    val scope = rememberCoroutineScope()
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 0
        }
    }

    AnimatedVisibility(showButton) {
        FloatingActionButton(onClick = {
            // 回到顶部
            scope.launch { 
                listState.animateScrollToItem(0)
            }
        }, modifier = Modifier.padding(16.dp)) {
            Icon(Icons.Default.KeyboardArrowUp, contentDescription = "回到顶部")
        }
    }
}

4、快速记忆

数据派生 -> 重组少 -> 性能优 -> 体验棒

5、常见错误

错误 后果 正确
不用 derivedStateOf 频繁重组 必须用
LaunchedEffect 无法驱动 UI by derivedStateOf
计算太重 卡 UI 移到 IO 线程

6、总结

derivedStateOf 是 Compose 中「计算优化 + 防闪烁」的最优解之一,掌握它能让你的UI更流畅。

rememberUpdatedState

我同样认为这不属于副作用函数,理由与derivedStateOf类似。但它与副作用LaunchedEffect函数配合紧密,所以一起介绍了。其实这里面涉及到一个闭包的概念。这里面就不展开讲,不了解的小伙伴去问AI吧。

1、一句话概括

rememberUpdatedState(value) = 「让 LaunchedEffect 里的协程,始终拿到最新的值」,即使它在重组时被捕获了旧值。

2、解决了什么问题

先看问题代码:

kotlin 复制代码
@Composable
fun GreetingMessage(message: String) {
    // 启动一个协程,每隔2秒打印一次 message
    LaunchedEffect(Unit) {
        while (true) {
            delay(2000)
            Log.d("message的值","当前 message: $message")  //  闭包捕获旧值
        }
    }
}

存在的问题:当 message 改变时,LaunchedEffect(Unit) 不会重启,因为key未发生变化; 因此协程里打印的仍然是"第一次 Composition"时的旧值; 这就叫「stale capture(过期捕获) 」。

解决方案:使用rememberUpdatedState进行修正

kotlin 复制代码
@Composable
fun UpdateNewMessage(message: String) {
    // 让协程中总能访问最新 message
    val currentMessage by rememberUpdatedState(message)

    LaunchedEffect(Unit) {
        while (true) {
            delay(2000)
            println("当前 message: $currentMessage")  // ✅ 始终是最新值
        }
    }
}

3、应用场景

  • 场景一:防抖(推荐此方式实现Compose的防抖)
kotlin 复制代码
@Composable
fun DebouncedSearch(
    query: String,
    onDebouncedQueryChange: (String) -> Unit
) {
    var searchQuery by remember { mutableStateOf(query) }
    val currentOnDebouncedQueryChange by rememberUpdatedState(onDebouncedQueryChange)

    LaunchedEffect(searchQuery) {
        delay(300) // 300ms 防抖延迟
        if(searchQuery.isNotBlank()) {
            currentOnDebouncedQueryChange(query)
        }

    }
    TextField( value = searchQuery,
        onValueChange = { searchQuery = it },
        label = { Text("搜索") } )
}
  • 场景二:事件监听中保持回调更新(最常见)
kotlin 复制代码
@Composable
fun LocationChange(onLocationUpdate: (Location) -> Unit) {
    val currentCallback by rememberUpdatedState(onLocationUpdate)
    
    DisposableEffect(Unit) {
        val listener = LocationListener { location ->
            currentCallback(location) // 使用最新的回调
        }
        
        locationManager.requestLocationUpdates(listener)
        
        onDispose {
            locationManager.removeUpdates(listener)
        }
    }
}

代码核心:时刻保持最新的回调,解决持有旧回调的问题

  • 场景三:定时器动态轮询(根据情况调整轮询时间)
kotlin 复制代码
/**
 * 每次调用调整轮询间隔,不触发重启
 */
@Composable
fun PollingData(intervalMs: Long) {
    val updatedInterval by rememberUpdatedState(intervalMs)
    var data by remember { mutableStateOf("加载中...") }

    LaunchedEffect(Unit) {
        while (true) {
            data = withContext(Dispatchers.IO) {
                RetrofitClient.apiService.getUsersById(1).toString()
            }
            delay(updatedInterval)  // 动态间隔
        }
    }

    Column {
        Text("数据: $data")
        Text("轮询间隔: $updatedInterval ms")
    }
}
  • 场景四:延迟提示(优化部分情况下,控件多次显示隐藏导致的闪烁)
kotlin 复制代码
@Composable
fun DelayedTooltip(visible: Boolean, text: String) {
    val updatedVisible by rememberUpdatedState(visible)

    LaunchedEffect(Unit) {
        delay(1000)  // 延迟显示
        if (updatedVisible) {
            showTooltip(text)
        }
    }

    // UI...
}

4、快速记忆

协程不重启,值要最新值,那就使用UpdatedState
LaunchedEffect(Unit) + rememberUpdatedState(value) = 不重启协程也能拿到最新值

5、常犯错误

错误 后果 正确
不用 rememberUpdatedState 协程用旧值 必须用
配合 LaunchedEffect(value) 频繁重启 Unit
SideEffect 每次重组都执行 错!

六、总结

rememberUpdatedState 是 Compose 中「协程 + 动态参数」的黄金搭档,经常与LaunchedEffect(Unit)搭配实现多场景功能。

总结:

Compose 的副作用函数本质是:在声明式UI中执行命令式逻辑的入口
LaunchedEffectDisposableEffectSideEffect 解决不同生命周期阶段的副作用;

rememberCoroutineScopeproduceStatederivedStateOfrememberUpdatedState 则是围绕状态、协程与性能优化的工具。理解它们的生命周期、触发时机与取消机制,是写好 Compose 的关键。

本文只列出了我能想到的一些场景,肯定有很多的遗漏,还是需要大家在使用中去完善了解

各副作用函数机制速查表:

函数 启动时机 重组触发 自动取消(随生命周期销毁) 常见用途 是否可持久运行(如轮询) 是否依赖 key
SideEffect 每次成功重组后执行 ✅ 每次重组都会执行 ❌ 不会自动取消 把 Compose 状态同步到非 Compose 系统(如 Log、ViewModel 状态等)
LaunchedEffect(key) 进入 Composition 时执行一次 ✅ key 变化会重启协程 ✅ key 改变或退出 Composition 时自动取消 执行一次性任务、启动协程、监听 Flow
DisposableEffect(key) 进入 Composition 时执行一次 ✅ key 变化或离开 Composition 时触发 onDispose ✅ 自动触发 onDispose 注册/注销监听、启动/停止外部资源
rememberCoroutineScope() 组合时创建一个可记忆的协程作用域 ❌ 不会因重组重新创建 ✅ Composition 离开时自动取消 需要在回调中手动启动协程(如按钮点击)
produceState() 进入 Composition 时启动协程 ✅ key 变化会重启生产逻辑 ✅ 自动取消旧协程 把异步任务结果转为 State(如网络请求)
derivedStateOf() 依赖 State 变化时计算 ✅ 依赖的值变化才重新计算 ❌(纯计算,此处无意义) 优化计算型状态,避免不必要重组
rememberUpdatedState() 每次重组更新内部值 ❌ 不会导致重组 让长期副作用中访问最新参数(如协程/回调)
snapshotFlow { } 在协程中把 State 转换为 Flow ✅ State 变化时发射新值 ✅ 协程取消即停止 响应式收集 State 变化

源码

github.com/tcyang12345...

相关推荐
LiuYaoheng5 分钟前
【Android】EventBus 的使用
android·java
2509_940880226 分钟前
Spring Cloud GateWay搭建
android·前端·后端
Haha_bj8 分钟前
一、Kotlin基础
android·kotlin
The Straggling Crow11 分钟前
缓存策略、批推理(batching)、异步 /并发机制
android·缓存
q***046331 分钟前
Spring Cloud Alibaba 组件版本选择
android·前端·后端
NeverLate_gogogo34 分钟前
幸运转盘 隐私政策
app
w***488237 分钟前
解决报错net.sf.jsqlparser.statement.select.SelectBody
android·前端·后端
k***38840 分钟前
MySQL 字符串日期格式转换
android·数据库·mysql
v***913043 分钟前
数据库高安全—openGauss安全整体架构&安全认证
android·前端·后端
Kapaseker1 小时前
Android分层没搞懂,外包转岗难成功
android·kotlin