安卓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
相关产品推荐
相关产品推荐

