LaunchedEffect的学习

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

    scss 复制代码
    var count by remember { mutableStateOf(0) }
    LaunchedEffect(count) {
        delay(1000)
        count++ // 触发键变化→重新执行→count++→无限循环
    }
  • ✅ 正确:加终止条件(如 if (count < 5) count++)。

坑 3:忽略协程取消的异常
  • 网络请求 / 文件操作被取消时,可能抛出 CancellationException,需捕获(非必须,但避免日志报错):

    kotlin

    scss 复制代码
    LaunchedEffect(Unit) {
        try {
            fetchData()
        } catch (e: CancellationException) {
            // 协程被取消,正常逻辑,无需处理
        } catch (e: Exception) {
            // 处理真实的业务异常(如网络错误)
            println("请求失败:${e.message}")
        }
    }
坑 4:滥用 LaunchedEffect(简单同步逻辑不用它)
  • ❌ 错误:用 LaunchedEffect 执行同步代码(如变量赋值):

    kotlin

    kotlin 复制代码
    LaunchedEffect(Unit) {
        val name = "张三" // 同步代码,无需协程
    }
  • ✅ 正确:同步逻辑直接写在 Composable 中,仅异步操作用 LaunchedEffect。

六、终极总结(3 个核心 + 1 个原则)

核心 1:作用

专门在 Composable 中执行挂起函数 / 异步操作,解决 "Composable 普通函数不能调用挂起函数" 的问题。

核心 2:生命周期

组件在→协程跑,组件没→协程停,绝对不会内存泄漏(最大优势)。

核心 3:触发键
  • Unit:仅执行 1 次(初始化场景);
  • 业务参数:参数变则重新执行(依赖更新场景);
  • 多个键:任意一个变就执行(多条件场景)。
核心原则
  • 「组件显示自动执行」的异步操作 → 用 LaunchedEffect;
  • 「手动触发(如点击)」的异步操作 → 用 rememberCoroutineScope。

简单记:LaunchedEffect = 组件的「自动异步任务器」,触发键是 "任务开关",生命周期是 "自动断电保护"。

相关推荐
alexhilton2 天前
模块化Jetpack Compose架构
android·kotlin·android jetpack
hai_android2 天前
InputStage 责任链核心执行流程
android·android studio·android jetpack
传奇开心果编程4 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程4 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程5 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
天空之城--7 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack
传奇开心果编程8 天前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
传奇开心果编程11 天前
【用案例学Material 3 Expressive】第1课:从一封邮件开始,感受安卓设计新语言的表现力
android·学习·ui·kotlin·android jetpack
传奇开心果编程11 天前
【Jetpack Compose进阶学与练】第14课:系列收尾复习总结;Compose项目常见坑点汇总;学习路线与后续学习方向
android·学习·ui·kotlin·android jetpack
hai_android12 天前
子 View 被压小时,如何向父容器"告状"?——MEASURED_STATE_TOO_SMALL 向上传递机制
android·kotlin·android jetpack