单线程协程中MediaPlayer抛出IllegalStateException问题排查
问题
我开发了一款猫用鼠标模拟器应用,屏幕中会显示一只跑动的老鼠,并随机发出吱吱声吸引猫咪注意。在单线程协程中调用PlaySound函数时,MediaPlayer的isPlaying方法抛出IllegalStateException异常。所有MediaPlayer相关操作都在该线程中执行,无法理解异常原因。若不使用协程直接调用该函数,虽能正常运行,但会阻塞主线程,导致老鼠动画停止更新。
相关代码
CoroutineScope(newSingleThreadContext("PlaySound")).launch{PlaySound(0)} private fun PlaySound(sound: Int) { when(sound){ 0 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeak) 1 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking1) 2 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking2) 3 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking3) 4 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking4) 5 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking5) 6 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking6) 7 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking7) 8 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking8) 9 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking9) 10 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking10) 11 -> currentMediaPlayer = MediaPlayer.create(gameContext,R.raw.mouse_squeaking10) } currentMediaPlayer.start() var metaD = ((currentMediaPlayer.duration.toLong()) * 0.8).toLong() Thread.sleep(metaD) if (currentMediaPlayer.isPlaying) { currentMediaPlayer.stop() } currentMediaPlayer.reset() currentMediaPlayer.release() }
Logcat错误信息
E FATAL EXCEPTION: PlaySound Process: info.mstitservices.mouse4cat, PID: 11539 java.lang.IllegalStateException at android.media.MediaPlayer.isPlaying(Native Method) at info.mstitservices.mouse4cat.Game.PlaySound(Game.kt:873) at info.mstitservices.mouse4cat.Game.access$PlaySound(Game.kt:31) at info.mstitservices.mouse4cat.Game$MouseSqueaks$1.invokeSuspend(Game.kt:664) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:462) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@9679fd3, java.util.concurrent.ScheduledThreadPoolExecutor@4038710[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]]
请问是否有合适的协程调用方式可以解决该问题?
解决方案
异常原因
- MediaPlayer状态非法:调用
isPlaying时,MediaPlayer可能因音频播放完成、系统回收或协程取消,进入了不允许执行该方法的状态(如已结束、已释放)。 - 协程与线程管理问题:手动创建的
newSingleThreadContext未被正确管理,易引发线程泄漏;Thread.sleep阻塞线程可能导致协程被取消,日志中的StandaloneCoroutine{Cancelling}也证实了这一点。 - 全局实例竞争:复用全局
currentMediaPlayer变量,多协程调用时会出现状态冲突。
修正方案
1. 改用系统协程调度器
放弃手动创建线程池,使用Dispatchers.IO处理音频操作,它是系统维护的IO密集型任务线程池,避免线程泄漏。
2. 用协程挂起延迟替代线程阻塞
用协程的delay挂起函数替换Thread.sleep,不会阻塞线程,更适配协程生命周期。
3. 使用独立MediaPlayer实例
每次播放创建独立的MediaPlayer对象,避免全局实例的状态冲突。
4. 增加异常捕获与状态保护
在调用MediaPlayer方法前做状态检查,捕获异常防止崩溃,确保资源最终被释放。
修正后的代码
// 推荐绑定生命周期的协程作用域,比如Activity/Fragment中使用lifecycleScope lifecycleScope.launch(Dispatchers.IO) { playSound(0) } private suspend fun playSound(sound: Int) { val mediaPlayer = when(sound){ 0 -> MediaPlayer.create(gameContext, R.raw.mouse_squeak) 1 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking1) 2 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking2) 3 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking3) 4 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking4) 5 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking5) 6 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking6) 7 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking7) 8 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking8) 9 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking9) 10, 11 -> MediaPlayer.create(gameContext, R.raw.mouse_squeaking10) else -> return } try { mediaPlayer.start() val delayDuration = (mediaPlayer.duration * 0.8).toLong() delay(delayDuration) if (mediaPlayer.isPlaying) { mediaPlayer.stop() } } catch (e: IllegalStateException) { e.printStackTrace() } finally { mediaPlayer.reset() mediaPlayer.release() } }
额外优化建议
- 若频繁播放短音频,推荐用
SoundPool替代MediaPlayer,它更适合短音频的低延迟播放场景,性能更优。 - 协程作用域尽量绑定页面生命周期(如
lifecycleScope),页面销毁时自动取消协程,避免内存泄漏。
内容的提问来源于stack exchange,提问作者MrMajorThorburn
相关产品推荐
相关产品推荐

