
本文详解在 Android 中可靠检测 MediaPlayer 音频播放结束的正确方法,指出 OnCompletionListener 失效的常见原因,并提供基于主线程轮询与状态判断的稳定替代方案。
本文详解在 android 中可靠检测 mediaplayer 音频播放结束的正确方法,指出 `oncompletionlistener` 失效的常见原因,并提供基于主线程轮询与状态判断的稳定替代方案。
在 Android 开发中,MediaPlayer 的 setOnCompletionListener() 理论上是监听音频自然播放结束的标准方式。但实践中,该回调常因以下原因失效:
- MediaPlayer 实例被重复创建或未正确复用(如每次点击都 new MediaPlayer()),导致旧监听器被丢弃;
- prepare() 或 prepareAsync() 调用后未成功进入 Prepared 状态,使播放流程异常;
- 播放中途调用 reset()、release() 或异常中断(如资源冲突、IO 错误),导致状态机脱离预期路径;
- 在 RecyclerView ViewHolder 中持有 MediaPlayer 引用时,因 ViewHolder 复用造成监听器绑定错位。
上述代码中,OnCompletionListener 未触发,极可能源于 mediaPlayer 实例在 onClick 中被频繁重建,而新实例并未重新设置监听器——原监听器仅绑定在已废弃的旧实例上。
✅ 推荐解决方案:主动轮询 + 状态协同判断
不依赖不可靠的回调,而是通过轻量级 UI 线程轮询,结合 isPlaying() 与自定义暂停标志,精准识别“播放自然结束”状态:
// 启动播放时重置状态
mediaPlayer.start();
isMediaPlayerPaused = false; // 全局布尔标志,记录用户主动暂停行为
new Thread(this::runPollingLoop).start();
private void runPollingLoop() {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable pollingTask = new Runnable() {
@Override
public void run() {
// 仅当非暂停且非播放中 → 视为播放完成
if (!mediaPlayer.isPlaying() && !isMediaPlayerPaused) {
// ✅ 执行播放结束逻辑
audioIsFinished = true;
releasePlayer();
myHolder.play.setImageResource(R.drawable.ic_baseline_play_arrow_24);
myHolder.divider.setVisibility(View.INVISIBLE);
myHolder.currentTime.setVisibility(View.INVISIBLE);
myHolder.seekBar.setVisibility(View.INVISIBLE);
}
// 每 10ms 检查一次(足够灵敏,且对主线程压力极小)
mainHandler.postDelayed(this, 10);
}
};
mainHandler.post(pollingTask);
}
⚠️ 关键注意事项:
- isMediaPlayerPaused 必须在 mediaPlayer.pause() 和 mediaPlayer.start() 时严格同步更新,否则会误判;
- 轮询间隔设为 10ms 是平衡精度与性能的合理值(人耳无法感知毫秒级延迟,且远低于典型帧率);
- releasePlayer() 中应确保 mediaPlayer.release() 并置空引用,避免内存泄漏;
- 若需支持 SeekBar 拖动后自动恢复播放,需在 seekTo() 后重置 isMediaPlayerPaused = false,防止拖动到末尾时误触发完成逻辑。
? 进阶建议:
对于复杂场景(如多音频队列、后台播放服务),推荐迁移到 ExoPlayer —— 其 Player.Listener 的 onPlaybackStateChanged() 与 isPlaying() 组合更健壮,并原生支持 STATE_ENDED 状态,大幅降低状态管理复杂度。
综上,放弃对 OnCompletionListener 的盲目依赖,采用“可控轮询 + 显式状态标记”策略,是保障音频结束逻辑稳定执行的务实之选。










