一、LaunchedEffect
提问:如果我想在页面一打开的时候,去发起一个网络请求拉取数据,或者弹Toast,这段代码应该写在哪里?
1.1 错误示范(直接写在函数里)
@Composable
fun UserProfile(userId: String) {
// 只要界面发生任何微小的刷新(重组),
// 这个函数就会被重新执行,网络请求会被疯狂发送无数次!
fetchUserDataFromNetwork(userId)
Text("用户信息")
}
在纯函数里执行这种“与 UI 渲染无关、且只应该执行一次或特定次数的操作”,就叫做副作用(Side Effect)。
1.2 正确的解决思路:使用 LaunchedEffect
我们需要一个“避风港”,告诉 Compose:“这段逻辑虽然写在 Composable 函数里,但请你把它隔离开,它有自己的生命周期。”
@Composable
fun UserProfile(userId: String) {
// LaunchedEffect 就是专门用来处理协程副作用的 API
LaunchedEffect(key1 = userId) {
// 这里的代码会在组件第一次显示到屏幕上时执行一次。
// 如果 userId 变了,它会取消之前的请求,重新执行一次。
// 其他任何无关的重组,都不会导致这里重新执行
fetchUserDataFromNetwork(userId)
}
Text("用户信息")
}
1.3 总结
因为 Composable 函数可能会在重组时被频繁、乱序地执行,所以绝对不能在组件中直接执行非纯函数的逻辑(如网络请求、数据库操作)。
必须使用 Compose 提供的副作用 API(如
LaunchedEffect),将其桥接到协程作用域中,并依赖特定的 Key 来控制执行时机,从而保证程序的稳定性。
二、LaunchedEffect 和协程
2.1 LaunchedEffect是什么
LaunchedEffect 不仅是“配合”协程,它本身就是一个协程启动器
在纯 Kotlin 协程里,我们要启动一个协程,通常需要写 scope.launch { ... }。
在 Compose 中,LaunchedEffect 的底层源码就是帮我们做了一件事:在组件进入屏幕(Enter the Composition)时,自动给我们提供一个安全的 CoroutineScope,并帮我们执行 launch。
2.2 为什么一定要跟“组件生命周期”绑定?
以前用 Java 开发 Android 的噩梦:
在一个 Activity 里发起了一个网络请求(用 Thread 或网络库),请求还没回来,用户按了返回键退出了页面(Activity 被销毁)。如果这时候请求回来了,还要去更新 UI,就会直接导致 Crash(NullPointerException 或 WindowLeaked)。为了解决这个问题,以前我们需要在
onDestroy()里手动去cancel所有的请求。
Compose 的优雅处理: 在 Compose 里,组件没有 onDestroy。一个 @Composable 函数可能因为一个 if 条件为 false,就直接从屏幕上消失了(这叫 Leave the Composition)。
这时候 LaunchedEffect 就起作用了:
- 启动: 当组件第一次被渲染时,
LaunchedEffect启动协程去拉取数据。 - 自动销毁: 一旦这个组件从屏幕上被移除了(Leave),Compose 框架会自动调用这个协程作用域的
cancel()。网络请求瞬间被掐断,绝对不会发生内存泄漏,也绝对不会导致 Crash,全程零手动干预。
2.3 LaunchedEffect 的 Key 机制(重启机制)
考虑这样一个衍伸场景::“如果组件还没销毁,但是我想重新请求一次数据怎么办?”
这就要看 LaunchedEffect 的参数了,比如 LaunchedEffect(userId) { ... }。
这里 userId 就是 Key(重启键)。 它的内部逻辑极其精妙:
- 进入组件,传入
userId = "张三",开启协程拉取张三的数据。 - 发生重组(比如点击了切换按钮),传入的
userId变成了"李四"。 LaunchedEffect发现 Key 发生变化了:-
它会立刻取消之前拉取张三数据的那个协程;
-
然后重新启动一个新的协程去拉取李四的数据。
-
2.4 总结
在 Compose 中如何做网络请求?Side Effect 是干什么的?”
在 Compose 中发起网络请求等挂起操作,必须使用
LaunchedEffect这样的 Side Effect API。它的本质是提供了一个与当前 Composable 生命周期强绑定的协程作用域 (CoroutineScope)。
当组件进入组合时,它会启动协程;当组件离开组合时,它会自动取消协程,从根本上杜绝了内存泄漏和异步回调更新 UI 导致的 Crash。
此外,它还自带基于
Key的重启机制。只要传入的 Key 发生改变,它会自动取消旧任务并启动新任务,这对于列表详情页的数据刷新场景非常契合。