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

安卓MediaPlayer播放完成后自动随机选曲循环播放故障排查

问题分析与修复方案

我帮你排查了下代码里的几个关键问题,这就是为什么你的MediaPlayer播放完初始音频后就停住了:

1. 文件描述符被提前关闭

在selectBordun()方法中,你获取RawResourceFd后立刻调用了afd.close(),这时候再调用player.setDataSource()使用的是已经关闭的文件描述符,系统根本无法读取到音频资源。必须在调用完setDataSource之后再关闭文件描述符。

2. 缺少MediaPlayer的准备步骤

调用player.reset()后,MediaPlayer回到了未初始化的状态,必须调用prepareAsync()(异步准备,避免阻塞线程)来让它加载新的音频资源。你代码里把prepareAsync()注释掉了,导致MediaPlayer一直处于未准备状态,永远不会触发OnPreparedListener,自然也就不会开始播放。

3. onStartCommand缺少返回值

Service的onStartCommand方法必须返回一个整数类型的状态值(比如START_STICKY),否则会影响Service的生命周期管理,甚至可能导致Service意外终止。

修正后的代码

下面是修复了上述问题的完整代码,同时优化了一些细节(比如避免重复设置监听器、使用更安全的协程上下文):

open class BordunService : Service() {
    private lateinit var mPlayer: MediaPlayer
    private lateinit var mPlayer2: MediaPlayer
    private lateinit var mPlayer3: MediaPlayer

    override fun onCreate() {
        super.onCreate()
        // 初始化每个MediaPlayer并设置通用监听器
        mPlayer = initMediaPlayer(R.raw.fping)
        mPlayer2 = initMediaPlayer(R.raw.fping) // 这里可以换成你需要的默认初始音频
        mPlayer3 = initMediaPlayer(R.raw.fping)
    }

    private fun initMediaPlayer(defaultResId: Int): MediaPlayer {
        val player = MediaPlayer.create(this, defaultResId)
        // 设置一次监听器即可,不用每次播放都重新设置
        player.setOnCompletionListener { completedPlayer ->
            println("completeplayer")
            selectBordun(completedPlayer)
        }
        player.setOnPreparedListener { preparedPlayer ->
            println("preparedplayer")
            preparedPlayer.start()
        }
        return player
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        if (intent.hasExtra("Play")) {
            playBordun()
        }
        // 返回START_STICKY保证Service被意外杀死后能重启
        return START_STICKY
    }

    private fun playBordun() {
        // 启动三个播放器,用MainScope更安全,避免GlobalScope的生命周期失控问题
        MainScope().launch {
            mPlayer.start()
            mPlayer2.start()
            mPlayer3.start()
        }
    }

    fun selectBordun(player: MediaPlayer) {
        println("selectstarted")
        player.reset()
        val randomGenerator = Random()
        val bordunInt = randomGenerator.nextInt(3) + 1

        val afd = when (bordunInt) {
            1 -> resources.openRawResourceFd(R.raw.cping)
            2 -> resources.openRawResourceFd(R.raw.ebping)
            3 -> resources.openRawResourceFd(R.raw.gping)
            else -> resources.openRawResourceFd(R.raw.fping) // 加载失败时的默认 fallback
        }

        try {
            player.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
            player.prepareAsync() // 异步准备,触发OnPreparedListener启动播放
        } catch (e: IOException) {
            e.printStackTrace()
            // 异常处理:回到默认音频避免播放中断
            player.reset()
            player.setDataSource(this, Uri.parse("android.resource://${packageName}/${R.raw.fping}"))
            player.prepareAsync()
        } finally {
            afd.close() // 确保文件描述符最终被关闭
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 销毁时释放所有MediaPlayer资源,避免内存泄漏
        mPlayer.release()
        mPlayer2.release()
        mPlayer3.release()
    }
}

额外优化点说明

  • 把MediaPlayer的监听器初始化逻辑抽成initMediaPlayer方法,避免每次播放重复设置监听器,代码更简洁易维护。
  • 使用MainScope()替代GlobalScope,因为GlobalScope的生命周期不受控制,容易引发内存泄漏。
  • 添加了异常捕获,防止音频加载失败导致的崩溃,同时提供了默认音频的 fallback 逻辑。
  • 在onDestroy中释放所有MediaPlayer资源,严格遵循Android媒体组件的生命周期规范。

内容的提问来源于stack exchange,提问作者BraveSentry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:14:37