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

单线程协程中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]]

请问是否有合适的协程调用方式可以解决该问题?


解决方案

异常原因

  1. MediaPlayer状态非法:调用isPlaying时,MediaPlayer可能因音频播放完成、系统回收或协程取消,进入了不允许执行该方法的状态(如已结束、已释放)。
  2. 协程与线程管理问题:手动创建的newSingleThreadContext未被正确管理,易引发线程泄漏;Thread.sleep阻塞线程可能导致协程被取消,日志中的StandaloneCoroutine{Cancelling}也证实了这一点。
  3. 全局实例竞争:复用全局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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:43:15