You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 05:25:01