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

Flutter关闭modalBottomSheet触发setState() called after dispose()异常

问题根因

点击外部区域关闭modalBottomSheet时,弹窗内的AudioPlayerWidget会被销毁并执行dispose()生命周期,但音频播放器的事件流订阅没有被取消,播放过程中持续推送的进度更新、时长变化等事件仍会触发回调,回调内的setState()作用在已销毁的组件上,就会抛出setState() called after dispose()异常。
现有代码仅在dispose中释放了_audioPlayer实例,既没有提前取消事件流订阅,也没有在调用setState前校验组件挂载状态,是异常出现的直接原因。

修复方案

1. 持有流订阅引用,组件销毁时主动取消

在_AudioPlayerWidgetState类中新增成员变量存储所有事件流的订阅对象:

// 存储音频事件流订阅
StreamSubscription<Duration> _durationSub;
StreamSubscription<Duration> _positionSub;
StreamSubscription<void> _completionSub;
StreamSubscription<String> _errorSub;

修改initState中的事件监听逻辑,将订阅返回值赋值给上述变量,同时在所有setState调用前增加组件挂载状态校验:

@override
void initState() {
  _audioPlayer = AudioPlayer(mode: PlayerMode.MEDIA_PLAYER);
  _audioCache = AudioCache(fixedPlayer: _audioPlayer);

  _durationSub = _audioPlayer.onDurationChanged.listen((d) {
    if (!mounted) return;
    setState(() {
      _duration = d;
    });
  });

  _positionSub = _audioPlayer.onAudioPositionChanged.listen((p) {
    if (!mounted) return;
    setState((){
      _position = p;
    });
  });

  _completionSub = _audioPlayer.onPlayerCompletion.listen((event) {
    if (!mounted) return;
    setState(() {
      _position = Duration(seconds: 0);
      _playerState = PlayerState.STOPPED;
    });
  });

  _errorSub = _audioPlayer.onPlayerError.listen((msg) {
    print('audioPlayer error : $msg');
    if (!mounted) return;
    setState(() {
      _playerState = PlayerState.STOPPED;
    });
  });
  super.initState();
}

重写dispose方法,按顺序先取消所有流订阅,再停止音频播放、释放播放器资源:

@override
void dispose() {
  // 取消所有事件订阅,阻断后续回调触发
  _durationSub?.cancel();
  _positionSub?.cancel();
  _completionSub?.cancel();
  _errorSub?.cancel();
  // 组件销毁时停止播放,避免后台无感知占用音频资源
  _audioPlayer?.stop();
  _audioPlayer?.dispose();
  super.dispose();
}

2. 全量setState调用增加挂载校验

所有存在异步等待逻辑的位置,在await返回后调用setState前,都必须先判断mounted属性为true,避免异步任务返回时组件已经销毁。
_playPause方法修改后如下:

_playPause() async {
  if (_playerState == PlayerState.PLAYING) {
    final playerResult = await _audioPlayer.pause();
    if (playerResult == 1 && mounted) {
      setState(() {
        _playerState = PlayerState.PAUSED;
      });
    }
  } else if (_playerState == PlayerState.PAUSED) {
    final playerResult = await _audioPlayer.resume();
    if (playerResult == 1 && mounted) {
      setState(() {
        _playerState = PlayerState.PLAYING;
      });
    }
  } else {
    if (widget.isAsset) {
      _audioPlayer = await _audioCache.play(widget.url);
      if(mounted){
        setState(() {
          _playerState = PlayerState.PLAYING;
        });
      }
    } else {
      final playerResult = await _audioPlayer.play(widget.url, isLocal: _isLocal);
      if (playerResult == 1 && mounted) {
        setState(() {
          _playerState = PlayerState.PLAYING;
        });
      }
    }
  }
}

_stop方法修改后如下:

_stop() async {
  final playerResult = await _audioPlayer.stop();
  if (playerResult == 1 && mounted) {
    setState(() {
      _playerState = PlayerState.STOPPED;
    });
  }
}
可选场景适配

如果业务需要弹窗关闭后音频继续后台播放,不需要在dispose中调用_audioPlayer.stop(),只需要将AudioPlayer实例上移到modalBottomSheet的父页面层级管理,播放器生命周期不随弹窗绑定即可,不会再触发该异常。


内容的提问来源于stack exchange,提问作者Laurent Thomas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 08:24:21