You need to enable JavaScript to run this app. Android屏幕旋转时MediaPlayer seekTo报错 无法保留播放进度
优惠活动
大模型
产品
解决方案
定价
更多

Android屏幕旋转时MediaPlayer seekTo报错 无法保留播放进度

崩溃根因定位

从堆栈信息可以直接定位到崩溃触发点:屏幕旋转触发Activity暂停流程时,在Ayuda.java:124行调用MediaPlayer.isPlaying()方法时抛出IllegalStateException。
这个异常来自Android MediaPlayer的状态机机制:MediaPlayer对不同状态下可调用的方法有严格限制,当实例处于Released(已释放)、Idle(刚重置未准备)、Error状态时,直接调用isPlaying()、getCurrentPosition()这类方法,就会直接触发崩溃。
结合代码逻辑,触发这个异常的具体场景是:

  • 音频播放完成后,在OnCompletionListener里调用了mediaPlayer.release(),此时MediaPlayer实例已经进入不可用的Released状态,但没有把这个实例的引用置空,也没有加状态判断
  • 屏幕旋转时Activity走暂停生命周期流程,代码直接拿到已经被释放的MediaPlayer实例调用isPlaying(),没有任何状态校验,直接触发崩溃
  • 当前的旋转恢复播放逻辑本身存在设计漏洞:屏幕旋转会导致Activity被完全销毁重建,重建流程里会new一个全新的空MediaPlayer实例,就算存了之前的播放位置,这个新实例连音频数据源都还没从Firebase异步拉取完成,根本无法恢复播放进度。
分步修复方案

1. 先解决无状态校验导致的崩溃

所有调用MediaPlayer方法的位置,必须增加前置状态判断,避免在不可用状态下调用方法。

  • 增加两个标记位记录MediaPlayer的运行状态:是否被释放、是否完成准备,避免直接依赖实例判断状态
// 类成员变量
private boolean isPlayerReleased = false;
private boolean isPlayerPrepared = false;
  • 初始化MediaPlayer时重置状态标记,在准备完成回调里同步更新状态
mediaPlayer = new MediaPlayer();
isPlayerReleased = false;
isPlayerPrepared = false;

mediaPlayer.setOnPreparedListener(mp -> {
    isPlayerPrepared = true; // 准备完成,更新标记
    mp.start();
});

mediaPlayer.setOnCompletionListener(mp -> {
    mp.release();
    isPlayerReleased = true; // 标记实例已释放
    isPlayerPrepared = false;
    mediaPlayer = null; // 置空引用,避免后续误调用
    cargar.setEnabled(true);
    pausa.setEnabled(false);
    stop.setEnabled(false);
});
  • 封装安全的MediaPlayer调用方法,替代原来直接调用原生方法的逻辑
// 安全判断是否正在播放
private boolean isPlayerPlaying() {
    if (mediaPlayer == null || isPlayerReleased) return false;
    try {
        // 只有完成准备的状态才能调用isPlaying
        if (isPlayerPrepared) {
            return mediaPlayer.isPlaying();
        }
    } catch (IllegalStateException e) {
        Log.e("MediaPlayer", "状态异常,无法获取播放状态");
    }
    return false;
}

// 安全获取当前播放位置
private int getCurrentSafePosition() {
    if (mediaPlayer == null || isPlayerReleased) return 0;
    try {
        if (isPlayerPrepared) {
            return mediaPlayer.getCurrentPosition();
        }
    } catch (IllegalStateException e) {
        Log.e("MediaPlayer", "状态异常,无法获取播放位置");
    }
    return 0;
}
  • 把原来代码里所有直接调用mediaPlayer.isPlaying()的位置(包括暂停按钮、停止按钮、onPause生命周期方法里的逻辑),全部替换为上面封装的安全方法。同时在加载音频的方法里,reset后同步重置准备标记:
public void cargarReproductor(View v) {
    mediaPlayer.reset();
    isPlayerPrepared = false; // reset后回到空闲状态,标记未准备
    try {
        mediaPlayer.setDataSource(ruta);
    } catch (IOException e) {
        e.printStackTrace();
    }
    mediaPlayer.prepareAsync();
    cargar.setEnabled(false);
    pausa.setEnabled(true);
    stop.setEnabled(true);
}

2. 修复屏幕旋转时的播放恢复逻辑

当前实现根本无法实现旋转后续播,因为Activity重建时会生成全新的空MediaPlayer实例,完全丢失之前的播放状态。有两个成熟的修复方案可以选择:

方案一(最低改造成本):禁止屏幕旋转时重建Activity

在AndroidManifest.xml里给Ayuda这个Activity加上配置变更声明,让系统在发生屏幕旋转、屏幕尺寸变化、键盘弹出这类配置变更时,不销毁重建Activity,直接回调onConfigurationChanged方法处理配置变化。这样MediaPlayer实例会一直保留在内存里,播放进度、播放状态完全不受旋转影响,根本不需要手动存进度恢复。
配置示例:

<activity
    android:name=".views.Ayuda"
    android:configChanges="orientation|screenSize|keyboardHidden" />

方案二(更规范的架构实现):用ViewModel持有MediaPlayer实例

如果需要Activity响应配置变化的逻辑,可以把MediaPlayer的初始化、状态管理逻辑全部放到ViewModel里。ViewModel的生命周期比Activity长,Activity被销毁重建时ViewModel不会被销毁,会一直持有MediaPlayer实例,重建完成的Activity只需要从ViewModel里拿到MediaPlayer实例,就可以直接恢复之前的播放状态,不会生成空实例。

3. 补充状态存储的兜底逻辑

如果选择走方案一之外的路径,要在onSaveInstanceState里同步存上音频的远程路径,不要只存播放位置:

@Override
protected void onSaveInstanceState(Bundle estadoGuardado) {
    super.onSaveInstanceState(estadoGuardado);
    estadoGuardado.putInt("posicion", getCurrentSafePosition());
    estadoGuardado.putString("audio_ruta", ruta); // 把音频路径也存下来,避免Firebase异步拉取的延迟导致重建时拿不到路径
}

这样就算MediaPlayer实例需要重新初始化,也能快速拿到之前存的路径和进度,完成播放恢复。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.07 18:15:03