ExoPlayer在低版本Android本地Raw视频播放时触发OOM问题
Compose中ExoPlayer在Android 8.1/9上播放本地Raw视频频繁触发OutOfMemory崩溃
问题概述
- 在Android 8.1、9版本的模拟器及真机上,使用Compose集成ExoPlayer播放Raw目录下的11秒/11.8MB MP4视频时,频繁触发OutOfMemory崩溃;API 31模拟器运行无异常。
- 空白测试项目单独运行播放器功能无崩溃,但将相同代码及依赖移入主App后必现崩溃,推测主App其他加载内容加剧了内存占用压力。
- 崩溃表现随机:播放片刻后崩溃、黑屏后崩溃、视频卡顿后崩溃,偶尔伴随协程错误(重现率约1/20)。
- 已尝试调整ExoPlayer加载控制(
setPrioritizeTimeOverSizeThresholds(false))、优化remember使用方式、更换视频加载方式,均无法解决问题。
核心实现代码
@Composable fun VideoView(video: Int = R.raw.app_onboard_full) { val context = LocalContext.current val exoPlayer = remember(context) { ExoPlayer.Builder(context) .setLoadControl(DefaultLoadControl.Builder() .setPrioritizeTimeOverSizeThresholds(false) .build()) .build() .also { ep -> ep.playWhenReady = true ep.repeatMode = Player.REPEAT_MODE_OFF val res = RawResourceDataSource.buildRawResourceUri(video) val dataSourceFactory = DefaultDataSource.Factory(context) val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(res)) ep.setMediaSource(mediaSource) ep.prepare() } } val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) DisposableEffect( AndroidView( factory = { PlayerView(context).apply { hideController() useController = false resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) player = exoPlayer } }, modifier = Modifier.fillMaxSize() ) ) { val observer = LifecycleEventObserver { owner, event -> when (event) { Lifecycle.Event.ON_PAUSE -> { exoPlayer.pause() } Lifecycle.Event.ON_RESUME -> { exoPlayer.play() } else -> {} } } val lifecycle = lifecycleOwner.value.lifecycle lifecycle.addObserver(observer) onDispose { exoPlayer.release() lifecycle.removeObserver(observer) } } }
解决方案建议
1. 优化ExoPlayer内存配置
Android 8.1/9的内存管理机制相对严格,可通过限制ExoPlayer的缓冲数据量降低内存占用:
val exoPlayer = remember(context) { ExoPlayer.Builder(context) .setRenderersFactory(DefaultRenderersFactory(context).setEnableDecoderFallback(true)) // 启用硬件解码降级策略 .setLoadControl(DefaultLoadControl.Builder() .setBufferDurationsMs( 1000, // 最小缓冲时长(ms) 5000, // 最大缓冲时长(ms) 500, // 缓冲到该时长后开始播放(ms) 500 // 缓冲不足时的重试等待时长(ms) ) .setPrioritizeTimeOverSizeThresholds(false) .build()) .build() .also { /* 初始化播放状态配置 */ } }
2. 修复Compose生命周期绑定逻辑
原代码中DisposableEffect的key使用AndroidView会导致生命周期监听逻辑异常,应改用exoPlayer和lifecycleOwner作为依赖key,同时补充销毁时的释放逻辑:
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current) DisposableEffect(exoPlayer, lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_PAUSE -> exoPlayer.pause() Lifecycle.Event.ON_RESUME -> exoPlayer.play() Lifecycle.Event.ON_DESTROY -> exoPlayer.release() // 确保页面销毁时释放播放器 } } val lifecycle = lifecycleOwner.value.lifecycle lifecycle.addObserver(observer) onDispose { exoPlayer.release() lifecycle.removeObserver(observer) } }
同时,将ExoPlayer的媒体资源初始化逻辑移至LaunchedEffect,避免在remember中同步执行导致内存高峰:
val exoPlayer = remember(context) { ExoPlayer.Builder(context) .setLoadControl(...) .build() .also { it.playWhenReady = true it.repeatMode = Player.REPEAT_MODE_OFF } } LaunchedEffect(exoPlayer) { val res = RawResourceDataSource.buildRawResourceUri(video) val dataSourceFactory = DefaultDataSource.Factory(context) val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(res)) exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() }
3. 主App内存排查与优化
- 使用Android Studio Profiler工具监控内存变化,定位OOM发生时的内存占用高峰模块,重点排查图片加载库(如Glide/Coil)的缓存配置、未释放的大对象。
- 在主App中播放视频时,暂停非必要的后台任务(如图片预加载、非紧急网络请求),减少内存竞争。
- 针对Android 8.1/9版本,可临时开启
android:largeHeap="true"缓解内存压力,但需结合内存优化从根源解决问题。
内容的提问来源于stack exchange,提问作者Gabi Matko
相关产品推荐
相关产品推荐

