Jetpack Compose中DisposableEffect正确使用及传感器监听异常问题
问题分析
你遇到的核心问题是屏幕方向变更触发了Activity重建:当跳转至PresetsEditor时,设置的横屏方向会导致Activity重新创建,旧的Composable实例被销毁,进而触发DisposableEffect的onDispose方法,提前调用viewModel.stopListeningSensor()停止了传感器。而ViewModel是与导航目的地绑定的(hiltViewModel()默认作用域为导航图),重建后不会重新初始化,传感器也就无法自动重启,最终表现为刚进入界面传感器就停止工作。
另外,你当前在ViewModel的init块中直接启动传感器的逻辑,会让传感器生命周期与ViewModel绑定,而非与Composable的可见性绑定,不符合“返回上一界面才停止监听”的需求。
解决方案
步骤1:完善ViewModel的传感器控制方法
移除init块中的自动启动逻辑,添加明确的启动/停止方法,同时在ViewModel销毁时兜底停止传感器:
@HiltViewModel class EditorViewModel @Inject constructor( private val sensor: AbstractSensor ) : ViewModel(){ private val _sensorFlow = MutableStateFlow(0f) val sensorFlow = _sensorFlow.asStateFlow() // 启动传感器监听 fun startListeningSensor() { sensor.startListening() sensor.setOnSensorValuesChangedListener { _sensorFlow.value = it } } // 停止传感器监听并清空回调 fun stopListeningSensor() { sensor.stopListening() sensor.setOnSensorValuesChangedListener(null) } // ViewModel销毁时兜底停止传感器,避免内存泄漏 override fun onCleared() { super.onCleared() stopListeningSensor() } }
步骤2:调整DisposableEffect逻辑,绑定传感器生命周期到Composable
修改PresetsEditor中的DisposableEffect,将传感器的启动/停止与Composable的进入/离开组合事件绑定,同时处理屏幕方向变更的场景:
@OptIn(ExperimentalLifecycleComposeApi::class) @Destination @Composable fun PresetsEditor( orientation: Int, presetsModel: PresetsModel, viewModel: EditorViewModel = hiltViewModel() ) { val context = LocalContext.current val systemUiController = LocalSystemUiController.current val dialogState by viewModel.dialogState.collectAsState() val steeringValue by viewModel.sensorFlow.collectAsStateWithLifecycle() systemUiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 以orientation作为DisposableEffect的key,确保方向变更时重新执行逻辑 DisposableEffect(orientation) { val activity = context.findActivity() ?: return@DisposableEffect onDispose {} val originalOrientation = activity.requestedOrientation // 1. 设置目标屏幕方向 activity.requestedOrientation = orientation // 2. 隐藏系统状态栏 systemUiController.isSystemBarsVisible = false // 3. 启动传感器监听 viewModel.startListeningSensor() onDispose { // 恢复原始屏幕方向 activity.requestedOrientation = originalOrientation // 显示系统状态栏 systemUiController.isSystemBarsVisible = true // 停止传感器监听 viewModel.stopListeningSensor() } } println("steeringValue: $steeringValue") EditorContent( presetsModel, dialogState, viewModel::onClickLabel, viewModel::onDismissRequest ) }
关键说明
- DisposableEffect的key设置:用
orientation作为key,当屏幕方向变更导致Composable重组时,旧的DisposableEffect会执行onDispose,新的实例会重新执行effect块,自动重启传感器。 - 传感器生命周期与Composable绑定:不再依赖ViewModel初始化逻辑启动传感器,而是在Composable进入组合时启动、离开时停止,完全匹配“进入界面监听,返回时停止”的需求。
- 兜底处理:ViewModel的
onCleared()方法确保即使出现异常场景,传感器也会被正确停止,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Afterglow
相关产品推荐
相关产品推荐

