前言
上一课学习了 rememberSaveable ,解决屏幕旋转、系统重建后状态丢失的问题。
前面所有代码都是同步UI渲染,但真实开发经常需要异步操作:页面进入时请求网络、读取本地存储、延时执行代码。
Compose可组合函数会频繁重组,直接在函数内部写异步代码会重复执行。本课学习副作用API,用来处理和UI渲染不同步的外部操作,重点掌握 LaunchedEffect ,了解 SideEffect 。
前置:完成1~8课,掌握状态、重组、remember系列API
一、完整可运行示例源码
kotlin
package com.example.composefirst
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
EffectDemoPage()
}
}
}
@Composable
fun EffectDemoPage() {
var count by remember { mutableIntStateOf(0) }
var tipText by remember { mutableStateOf("等待操作") }
// SideEffect:每次成功重组完成后执行
SideEffect {
println("页面发生一次重组,count = $count")
}
// LaunchedEffect:启动协程,key变化时重启协程
LaunchedEffect(count) {
tipText = "计数发生变化,延时2秒重置提示"
delay(2000)
tipText = "等待操作"
}
Column(modifier = Modifier.padding(20.dp)) {
Text(text = "当前计数:$count")
Text(text = "提示信息:$tipText")
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = { count++ }) {
Text("+1")
}
}
}
@Preview
@Composable
fun EffectPreview() {
EffectDemoPage()
}
二、逐行代码解读
- SideEffect
SideEffect 在每一次重组成功之后执行。
可组合函数每次重组完成,就会运行SideEffect内部代码。适合同步副作用,比如打印日志、同步更新外部非Compose状态。
⚠️ 注意:不要在SideEffect写耗时、阻塞代码,它不能启动协程。 - LaunchedEffect(key)
LaunchedEffect 是Compose最常用的异步副作用API。
- 接收一个key参数;
- 进入组合(组件首次渲染)时,自动启动协程;
- key值发生变化:旧协程自动取消,重新启动新协程;
- 组件退出组合(页面销毁、组件不再渲染):协程自动取消,防止内存泄漏。
示例中key是 count :每次count数值改变,就触发协程。
delay(2000) 是协程延时函数,不会阻塞UI主线程。延时结束修改tipText状态,触发界面刷新。
- 易错点:不要直接在Compose顶层写delay、网络请求
kotlin
// ❌ 错误写法,每次重组都会执行,无限重复请求
@Composable
fun BadDemo() {
delay(1000)
Text("test")
}
可组合函数会反复重组,直接写异步代码会多次重复执行,必须放到LaunchedEffect内部。
三、两个API适用场景区分
SideEffect:同步副作用,每次重组完成后运行,没有协程,适合日志、同步外部状态。
LaunchedEffect:带协程的异步副作用,支持delay、网络请求,依靠key控制启停,组件销毁自动取消协程,是开发最常用。
四、综合练习题
填空题
- Compose中用来启动协程、处理异步任务的副作用API是________。
- ________会在每一次重组成功之后执行,不支持协程。
- LaunchedEffect的________发生变化时,会取消旧协程并重新启动协程。
答案与解析
- LaunchedEffect ;解析:LaunchedEffect内置协程,专门处理异步任务。
- SideEffect ;解析:SideEffect在重组完成后执行,只支持同步代码。
- key;解析:key作为依赖标记,key改变触发协程重启。
判断题
- LaunchedEffect内部可以使用delay,不会阻塞UI主线程。()
答案:正确。理由:LaunchedEffect启动协程,delay是挂起函数,不会阻塞主线程。
- 可组合函数里面直接写网络请求,每次重组都会重复发起请求。()
答案:正确。理由:Compose可组合函数会多次重组,直接写请求会重复执行,必须放到LaunchedEffect。
- SideEffect内部可以写delay、网络请求这类异步挂起函数。()
答案:错误。理由:SideEffect没有协程上下文,不能调用suspend挂起函数。
简答题
- LaunchedEffect的key参数作用是什么?组件销毁时协程会发生什么?
参考答案:
key是依赖标记,key值改变,会取消当前正在运行的协程,重新启动新协程。当组件从UI树移除(页面关闭),LaunchedEffect会自动取消协程,避免内存泄漏。
代码实操题
编写代码:页面一加载,就延时3秒修改提示文字为「页面加载完成」,只执行一次,点击按钮修改count不会再次触发这个延时任务。参考答案
kotlin
@Composable
fun LoadDemoPage() {
var tip by remember { mutableStateOf("页面加载中...") }
var count by remember { mutableIntStateOf(0) }
// key写固定常量,只在组件首次进入组合执行一次
LaunchedEffect(Unit) {
delay(3000)
tip = "页面加载完成"
}
Column(modifier = Modifier.padding(20.dp)) {
Text(tip)
Text("count:$count")
Button(onClick = { count++ }) {
Text("+1")
}
}
}
解析:key传入 Unit ,代表依赖永远不变,只在组件首次挂载运行一次;修改count不会触发这个LaunchedEffect重启。
五、本课小结
- SideEffect:重组完成后执行同步代码,无协程。
- LaunchedEffect:启动协程处理异步任务,key控制执行时机,组件销毁自动取消协程。
- 不要在可组合函数顶层直接写耗时、网络请求代码。
- key填Unit,代表只在页面首次加载执行一次。
下一课:点击事件进阶、Dropdown下拉菜单,状态联动综合练习。