LaunchedEffect 完整使用总结(核心 + 场景 + 避坑)
LaunchedEffect 是 Compose 中唯一绑定组件生命周期的协程启动器,专门解决「在 Composable 中执行异步 / 挂起操作」的问题,下面从「核心定义、使用规则、典型场景、避坑指南」四个维度做终极总结,新手也能直接套用。
一、核心定义(一句话记住)
LaunchedEffect 是 Compose 提供的协程作用域:
- 组件「组合完成(显示在屏幕)」→ 自动启动协程,执行内部挂起函数(delay、网络请求等);
- 组件「组合销毁(从屏幕消失)」→ 自动取消协程,避免内存泄漏;
- 「触发键」变化 → 取消旧协程,重新启动新协程(精准控制执行时机)。
基础语法
ini
LaunchedEffect(
key1 = 触发键1, // 必传:单个触发键
key2 = 触发键2, // 可选:多个触发键(任意一个变就重新执行)
block = {
// 协程作用域:可调用所有挂起函数(delay、retrofit、room等)
// 代码仅在「触发键变化/组件首次显示」时执行
}
)
二、核心规则(触发键 + 生命周期)
1. 触发键(最核心):决定执行时机
触发键是 LaunchedEffect 的 "开关",不同写法对应不同执行逻辑,用表格总结(新手直接记):
| 触发键写法 | 执行时机 | 典型场景 |
|---|---|---|
LaunchedEffect(Unit) |
组件首次显示执行 1 次,后续重组 / 触发键不变时不执行 | 页面初始化请求数据、一次性倒计时 |
LaunchedEffect(userId) |
首次显示执行 + userId 变化时重新执行 | 切换用户加载信息、切换筛选条件刷新列表 |
LaunchedEffect(isShow) |
isShow 为 true 时执行,false 时取消协程 | 弹窗显示时启动轮播、隐藏时停止 |
LaunchedEffect(key1, key2) |
任意一个触发键变化,就重新执行 | 多条件筛选(类型 + 时间)刷新数据 |
2. 生命周期绑定:绝对不会内存泄漏
- 组件在屏幕上 → 协程正常运行;
- 组件被销毁(比如返回上一页、弹窗关闭)→ 协程立刻取消,内部挂起函数(如网络请求)也会终止;
- 对比:
rememberCoroutineScope启动的协程不会随组件销毁自动取消,需手动管理,而LaunchedEffect是 "全自动"。
三、典型使用场景(直接套用)
场景 1:页面初始化异步操作(最常用)
页面显示后,一次性请求数据 / 加载缓存(触发键 = Unit,仅执行 1 次):
kotlin
@Composable
fun UserListScreen() {
val viewModel = viewModel<UserViewModel>()
val listState by viewModel.userList.collectAsState()
// 页面首次显示,请求用户列表(仅执行1次)
LaunchedEffect(Unit) {
viewModel.fetchUserList() // 内部是 suspend 函数(网络请求)
}
LazyColumn {
items(listState) { user -> Text(user.name) }
}
}
场景 2:依赖参数变化的异步操作
参数(如筛选条件、用户 ID)变化时,重新执行异步操作:
kotlin
@Composable
fun FilteredListDemo() {
var filterType by remember { mutableStateOf("全部") }
val viewModel = viewModel<FilterViewModel>()
// filterType 变化时,重新请求筛选后的列表
LaunchedEffect(filterType) {
viewModel.fetchFilteredList(filterType)
}
Column {
// 切换筛选条件
Button(onClick = { filterType = "已完成" }) { Text("已完成") }
// 显示列表...
}
}
场景 3:定时任务(倒计时 / 轮播)
组件显示时启动定时任务,销毁时自动停止:
kotlin
import androidx.compose.runtime.*
@Composable
fun CountdownDemo() {
var count by remember { mutableStateOf(5) }
// 倒计时:每秒减1,直到count=0
LaunchedEffect(count) {
if (count > 0) {
delay(1000)
count--
}
}
Text(if (count > 0) "倒计时:$count" else "倒计时结束")
}
场景 4:配合侧效的一次性操作
比如:状态变化后,弹出 Toast / 导航跳转(依赖状态触发,仅执行 1 次):
kotlin
@Composable
fun LoginSuccessDemo() {
val context = LocalContext.current
val navController = rememberNavController()
val loginState by viewModel.loginState.collectAsState()
// 登录成功后,弹出Toast并跳转到首页(仅触发1次)
LaunchedEffect(loginState.isSuccess) {
if (loginState.isSuccess) {
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show()
navController.navigate("home")
// 重置状态,避免重复跳转
viewModel.resetLoginState()
}
}
// 登录按钮...
}
四、关键对比(避免用错)
新手容易混淆 LaunchedEffect 和 rememberCoroutineScope,用表格清晰区分:
| 工具 | 生命周期 | 执行时机 | 典型场景 |
|---|---|---|---|
LaunchedEffect |
绑定组件 | 自动执行(触发键控制) | 页面初始化、定时任务、参数依赖的异步操作 |
rememberCoroutineScope |
不绑定组件 | 手动调用(如点击事件) | 按钮点击触发的异步操作(跳转、提交) |
示例:点击按钮触发异步操作(用 rememberCoroutineScope 更合适):
kotlin
@Composable
fun SubmitButton() {
val scope = rememberCoroutineScope()
Button(onClick = {
// 点击触发,手动启动协程
scope.launch {
submitData() // 挂起函数
}
}) {
Text("提交")
}
}
五、避坑指南(新手必看)
坑 1:触发键设置错误,导致重复执行 / 不执行
- ❌ 错误:用「可变对象」做触发键(如
LaunchedEffect(mutableList))→ 对象引用不变,即使内容变也不重新执行; - ✅ 正确:用「不可变基础类型 / 数据类」(String、Int、data class),比如
LaunchedEffect(list.size)或LaunchedEffect(filterType)。
坑 2:在 LaunchedEffect 中直接修改 State,导致无限重组
-
❌ 错误:无限制修改触发键依赖的 State:
kotlin
scssvar count by remember { mutableStateOf(0) } LaunchedEffect(count) { delay(1000) count++ // 触发键变化→重新执行→count++→无限循环 } -
✅ 正确:加终止条件(如
if (count < 5) count++)。
坑 3:忽略协程取消的异常
-
网络请求 / 文件操作被取消时,可能抛出
CancellationException,需捕获(非必须,但避免日志报错):kotlin
scssLaunchedEffect(Unit) { try { fetchData() } catch (e: CancellationException) { // 协程被取消,正常逻辑,无需处理 } catch (e: Exception) { // 处理真实的业务异常(如网络错误) println("请求失败:${e.message}") } }
坑 4:滥用 LaunchedEffect(简单同步逻辑不用它)
-
❌ 错误:用
LaunchedEffect执行同步代码(如变量赋值):kotlin
kotlinLaunchedEffect(Unit) { val name = "张三" // 同步代码,无需协程 } -
✅ 正确:同步逻辑直接写在 Composable 中,仅异步操作用
LaunchedEffect。
六、终极总结(3 个核心 + 1 个原则)
核心 1:作用
专门在 Composable 中执行挂起函数 / 异步操作,解决 "Composable 普通函数不能调用挂起函数" 的问题。
核心 2:生命周期
组件在→协程跑,组件没→协程停,绝对不会内存泄漏(最大优势)。
核心 3:触发键
Unit:仅执行 1 次(初始化场景);- 业务参数:参数变则重新执行(依赖更新场景);
- 多个键:任意一个变就执行(多条件场景)。
核心原则
- 「组件显示自动执行」的异步操作 → 用
LaunchedEffect; - 「手动触发(如点击)」的异步操作 → 用
rememberCoroutineScope。
简单记:LaunchedEffect = 组件的「自动异步任务器」,触发键是 "任务开关",生命周期是 "自动断电保护"。