Jetpack Compose切换页面时ExoPlayer销毁延迟问题排查
Jetpack Compose切换页面时ExoPlayer画面残留问题分析与修复
可能的原因
- 生命周期监听逻辑错误:
ExoPlayerLifecycleOwner中把AndroidView作为DisposableEffect的key,导致播放器的生命周期绑定到View的创建销毁,而非页面生命周期。页面跳转时,原页面进入ON_PAUSE仅调用exoPlayer.stop(),但播放器View未立即隐藏,加上Compose重组滞后,画面残留到新页面。 - Compose重组与原生View销毁延迟:Compose的UI重组是异步的,页面跳转时
VideoPlayer的isPlaying状态无法立即更新,原生StyledPlayerView销毁有延迟,过渡阶段画面会短暂显示在新页面。 - 播放器资源释放不及时:
ExoPlayer.release()仅在DisposableEffect的onDispose中调用,若DisposableEffect因key设置问题未及时触发,播放器持续持有资源,View无法及时销毁。
修复方案
1. 修正生命周期监听逻辑
将ExoPlayerLifecycleOwner的DisposableEffectkey改为页面生命周期所有者和播放器实例,确保生命周期变化时正确控制播放器,并在页面销毁时释放资源:
@Composable internal fun ExoPlayerLifecycleOwner(exoPlayer: ExoPlayer) { val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) DisposableEffect(lifecycleOwner.value, exoPlayer) { val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_RESUME -> { if (exoPlayer.playWhenReady) exoPlayer.play() } Lifecycle.Event.ON_PAUSE -> exoPlayer.pause() Lifecycle.Event.ON_STOP -> exoPlayer.stop() Lifecycle.Event.ON_DESTROY -> exoPlayer.release() else -> {} } } val lifecycle = lifecycleOwner.value.lifecycle lifecycle.addObserver(observer) onDispose { lifecycle.removeObserver(observer) if (exoPlayer.isPlaying) exoPlayer.stop() exoPlayer.release() } } AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> StyledPlayerView(context).apply { resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT player = exoPlayer } } ) }
2. 监听页面离开状态,强制重置播放状态
在VideoPlayer中添加页面生命周期监听,进入ON_PAUSE时立即把isPlaying设为false,让Compose快速切换到封面图:
@Composable private fun VideoPlayer(videoUrl: String) { val context = LocalContext.current var isPlaying by remember { mutableStateOf(false) } val videoUri = Uri.parse(videoUrl) val lifecycleOwner = LocalLifecycleOwner.current val cacheDataSource = CacheDataSourceFactory().createCacheDataSource() val exoPlayer = remember { ExoPlayer.Builder(context).build().apply { val mediaSource = ProgressiveMediaSource.Factory(cacheDataSource) .createMediaSource(MediaItem.fromUri(videoUri)) setMediaSource(mediaSource) playWhenReady = false prepare() } } // 页面离开时重置播放状态 DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_PAUSE) isPlaying = false } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } LaunchedEffect(isPlaying) { if (isPlaying) { exoPlayer.seekToDefaultPosition() exoPlayer.play() } else { exoPlayer.pause() } } // 原有UI逻辑保持不变 if (isPlaying) { Box( modifier = Modifier .height(Constants.dimensions.d220) .padding(vertical = SynergyTheme.dimensions.x2_5) .clip(SynergyTheme.shapes.medium), ) { ExoPlayerLifecycleOwner(exoPlayer) } } else { Box { ImageFromSource( iconSource = IconSource.FromResource( resourceId = R.drawable.about_synergy, contentDescription = null, ), modifier = Modifier .fillMaxWidth() .padding(vertical = SynergyTheme.dimensions.x2_5) .clip(SynergyTheme.shapes.medium), contentScale = ContentScale.FillWidth, ) Image( modifier = Modifier .size(Constants.dimensions.d56) .align(Alignment.Center) .clickable { isPlaying = true }, painter = painterResource(id = R.drawable.about_play_video), contentDescription = null, contentScale = ContentScale.Crop, ) } } }
3. 确保页面切换时View及时隐藏
通过上述生命周期监听,页面跳转时isPlaying会被立即设为false,Compose会快速切换到封面图,避免播放器View在过渡阶段显示。
内容的提问来源于stack exchange,提问作者Ruslan
相关产品推荐
相关产品推荐

