DisposableEffect的使用:

7 阅读5分钟

一、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. 依赖项设计原则

  • 仅将 “影响副作用逻辑” 的变量作为依赖项(如 userIdisListening);

  • 避免将可变状态(如 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. 避免内存泄漏

  • 清理逻辑必须释放所有持有的引用(如 ContextActivityView);

  • 优先使用 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++
}

六、核心总结

  1. 核心定位DisposableEffect 是 Compose 中处理 “需清理资源” 副作用的核心 API,强制绑定 “执行 → 清理” 逻辑;

  2. 核心语法DisposableEffect(依赖项) { 副作用逻辑; onDispose { 清理逻辑 } }

  3. 适用场景:注册 / 注销监听、申请 / 释放资源、订阅 / 取消订阅数据流;

  4. 关键原则

    • 依赖项仅包含影响副作用的变量;
    • 清理逻辑必须释放所有资源,避免内存泄漏;
    • 避免在副作用内直接更新 State,防止无限重组。

DisposableEffect 的核心价值是保证资源的 “申请 - 释放” 闭环,是 Compose 中处理带清理逻辑副作用的唯一选择,相比传统 Android 的 onCreate/onDestroy,它更贴合 Compose 的重组生命周期,能有效避免资源泄漏。