LaunchedEffect的学习

LaunchedEffect 完整使用总结(核心 + 场景 + 避坑)

LaunchedEffect 是 Compose 中唯一绑定组件生命周期的协程启动器,专门解决「在 Composable 中执行异步 / 挂起操作」的问题,下面从「核心定义、使用规则、典型场景、避坑指南」四个维度做终极总结,新手也能直接套用。

一、核心定义(一句话记住)

LaunchedEffectCompose 提供的协程作用域

  • 组件「组合完成(显示在屏幕)」→ 自动启动协程,执行内部挂起函数(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()
        }
    }

    // 登录按钮...
}

四、关键对比(避免用错)

新手容易混淆 LaunchedEffectrememberCoroutineScope,用表格清晰区分:

工具 生命周期 执行时机 典型场景
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 = 组件的「自动异步任务器」,触发键是 "任务开关",生命周期是 "自动断电保护"。

相关推荐
阿巴斯甜6 小时前
observeAsState和collectAsStateWithLifecycle 的区别:
android jetpack
Fate_I_C10 小时前
Android现代开发:Kotlin&Jetpack
android·开发语言·kotlin·android jetpack
RainyJiang20 小时前
谱写Kotlin协程面试进行曲-进阶篇(第二乐章)
面试·kotlin·android jetpack
星霜笔记1 天前
GitMob — 手机端 GitHub 管理工具
android·kotlin·github·android jetpack
alexhilton3 天前
Compose中的ContentScale:终极可视化指南
android·kotlin·android jetpack
阿巴斯甜3 天前
Compose中CompositionLocal 的使用
android jetpack
阿巴斯甜3 天前
Compose中 MutableState的状态区别:
android jetpack
段娇娇3 天前
Android jetpack LiveData (三) 粘性数据(数据倒灌)问题分析及解决方案
android·android jetpack
段娇娇4 天前
Android jetpack LiveData(一)使用篇
android·android jetpack