一、DisposableEffect 核心定义(基于 Compose 1.6.0+ 最新 API)
DisposableEffect 是 Compose 中用于处理需要 “副作用执行 + 清理” 的核心副作用 API,属于组合式副作用(Composable Side Effect),核心特征是:
- 当组件进入组合(Compose)时执行副作用逻辑;
- 当组件退出组合(Dispose)或依赖项变化时执行清理逻辑;
- 必须在
@Composable函数内使用,且强制要求实现清理逻辑(避免资源泄漏)。
核心适用场景
- 注册 / 注销监听器(如系统广播、传感器监听、View 点击监听);
- 申请 / 释放临时资源(如 Camera、MediaPlayer、网络连接);
- 订阅 / 取消订阅数据流(如非生命周期感知的 Flow、第三方 SDK 回调);
- 执行一次性操作且需确保退出组合时清理(如弹窗监听、权限监听)。
二、核心语法与参数解析
1. 基础语法
@Composable
fun DisposableEffectDemo() {
// 依赖项:key1、key2 变化时,先执行清理逻辑,再重新执行副作用逻辑
DisposableEffect(key1, key2) {
// 第一步:进入组合/依赖项变化时执行的副作用逻辑(如注册监听)
val listener = MyListener()
registerListener(listener)
// 第二步:必须返回 onDispose 块(清理逻辑,强制要求)
onDispose {
// 退出组合/依赖项变化时执行(如注销监听)
unregisterListener(listener)
}
}
}
2. 关键参数说明
| 参数 | 作用 | 示例 |
|---|---|---|
keys | 依赖项数组(可变参数),值变化时触发 “清理 → 重新执行副作用” | DisposableEffect(userId, isActive) |
effect | 副作用逻辑块,必须返回 onDispose 清理块(编译期强制检查) | 注册监听、初始化资源 |
onDispose | 清理逻辑块,组件退出组合 / 依赖项变化时执行,用于释放资源、注销监听等 | 注销监听、关闭资源、取消订阅 |
3. 特殊场景:无依赖项(仅执行一次)
如果依赖项传 Unit,则副作用仅在组件首次进入组合时执行,退出组合时执行清理(全程仅一次):
DisposableEffect(Unit) {
// 仅首次进入组合执行
initResource()
onDispose {
// 仅退出组合时执行
releaseResource()
}
}
三、完整使用示例(覆盖核心场景)
前置说明
所有示例均基于 Compose 1.6.0+,需确保导入核心包:
场景 1:注册 / 注销系统广播(最典型场景)
// 监听屏幕亮灭广播示例
@Composable
fun ScreenStateMonitor() {
val context = LocalContext.current
// 定义广播接收器(需在 DisposableEffect 内初始化,避免重复创建)
lateinit var screenReceiver: BroadcastReceiver
DisposableEffect(context) {
// 第一步:注册广播(进入组合时执行)
screenReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_SCREEN_ON -> println("屏幕亮了")
Intent.ACTION_SCREEN_OFF -> println("屏幕灭了")
}
}
}
// 注册广播
val filter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_SCREEN_OFF)
}
context.registerReceiver(screenReceiver, filter)
// 第二步:注销广播(退出组合时执行)
onDispose {
context.unregisterReceiver(screenReceiver)
println("广播已注销")
}
}
}
场景 2:订阅 / 取消订阅 Lifecycle 事件
@Composable
fun LifecycleMonitor() {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
// 第一步:注册生命周期监听
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
println("生命周期事件:$event")
}
lifecycleOwner.lifecycle.addObserver(observer)
// 第二步:取消监听
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
println("生命周期监听已移除")
}
}
}
场景 3:依赖项变化时重新执行副作用
// 监听不同 userId 的数据更新,userId 变化时重新订阅
@Composable
fun UserDataMonitor(userId: String) {
DisposableEffect(userId) {
// 第一步:根据当前 userId 订阅数据
val subscription = subscribeUserData(userId) { data ->
println("用户 $userId 数据更新:$data")
}
// 第二步:userId 变化/退出组合时取消订阅
onDispose {
subscription.unsubscribe()
println("用户 $userId 订阅已取消")
}
}
}
// 模拟订阅函数
fun subscribeUserData(userId: String, callback: (String) -> Unit): Subscription {
println("开始订阅用户 $userId 数据")
return object : Subscription {
override fun unsubscribe() {
println("取消订阅用户 $userId 数据")
}
}
}
interface Subscription {
fun unsubscribe()
}
场景 4:与 LaunchedEffect 对比(处理需清理的协程)
LaunchedEffect 仅执行协程且自动取消,但无显式清理逻辑;DisposableEffect 可结合 CoroutineScope 实现 “协程执行 + 资源清理”:
@Composable
fun CoroutineWithCleanup() {
val scope = rememberCoroutineScope()
var job by remember { mutableStateOf<kotlinx.coroutines.Job?>(null) }
DisposableEffect(Unit) {
// 执行协程任务
job = scope.launch {
repeat(10) {
println("协程执行中:$it")
kotlinx.coroutines.delay(1000)
}
}
// 退出组合时取消协程 + 清理资源
onDispose {
job?.cancel()
println("协程已取消")
// 额外清理逻辑(如关闭网络连接)
}
}
}
四、DisposableEffect 与其他副作用 API 的核心区别
| API 名称 | 核心特征 | 清理逻辑 | 适用场景 |
|---|---|---|---|
DisposableEffect | 组合 / 依赖变化执行副作用,强制清理 | 必须实现 onDispose | 需注册 / 注销、申请 / 释放资源的场景 |
LaunchedEffect | 协程内执行副作用,自动取消协程 | 自动取消协程(无显式清理) | 仅需执行协程、无需额外清理的场景 |
SideEffect | 每次重组都执行,无清理逻辑 | 无 | 轻量、无需清理的一次性操作 |
rememberUpdatedState | 保存最新值,供副作用使用 | 无 | 副作用中引用最新值(避免依赖项变化) |
五、关键使用原则与避坑指南
1. 强制清理逻辑
DisposableEffect 的 effect 块必须返回 onDispose,否则编译报错。即使暂时无清理逻辑,也需空实现:
kotlin
DisposableEffect(Unit) {
// 副作用逻辑
onDispose {
// 空清理块(避免编译错误)
}
}
2. 依赖项设计原则
-
仅将 “影响副作用逻辑” 的变量作为依赖项(如
userId、isListening); -
避免将可变状态(如
mutableStateOf)直接作为依赖项,优先用rememberUpdatedState包装最新值:kotlin
@Composable fun OptimizeDependency(callback: () -> Unit) { // 保存最新的 callback,避免 callback 变化触发副作用重新执行 val latestCallback = rememberUpdatedState(callback) DisposableEffect(Unit) { val listener = { latestCallback.value() } registerListener(listener) onDispose { unregisterListener(listener) } } }
3. 避免内存泄漏
-
清理逻辑必须释放所有持有的引用(如
Context、Activity、View); -
优先使用
LocalContext.current.applicationContext而非 Activity 上下文:kotlin
val context = LocalContext.current val appContext = context.applicationContext // 避免持有 Activity 引用
4. 不要在 DisposableEffect 内更新 State
副作用内直接更新 mutableStateOf 会触发重组,可能导致无限循环。如需更新状态,优先用 LaunchedEffect 或限制依赖项:
kotlin
// 错误示例:可能触发无限重组
DisposableEffect(Unit) {
count++ // count 是 mutableStateOf(0)
onDispose { }
}
// 正确示例:用 LaunchedEffect 执行状态更新
LaunchedEffect(Unit) {
count++
}
六、核心总结
-
核心定位:
DisposableEffect是 Compose 中处理 “需清理资源” 副作用的核心 API,强制绑定 “执行 → 清理” 逻辑; -
核心语法:
DisposableEffect(依赖项) { 副作用逻辑; onDispose { 清理逻辑 } }; -
适用场景:注册 / 注销监听、申请 / 释放资源、订阅 / 取消订阅数据流;
-
关键原则:
- 依赖项仅包含影响副作用的变量;
- 清理逻辑必须释放所有资源,避免内存泄漏;
- 避免在副作用内直接更新 State,防止无限重组。
DisposableEffect 的核心价值是保证资源的 “申请 - 释放” 闭环,是 Compose 中处理带清理逻辑副作用的唯一选择,相比传统 Android 的 onCreate/onDestroy,它更贴合 Compose 的重组生命周期,能有效避免资源泄漏。