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

安卓聊天应用适配器更新时MediaPlayer重复加载中断播放如何解决

问题根因

你当前的MediaPlayer实例创建、初始化逻辑全部放在了Adapter的item绑定流程中,每当列表刷新(新消息触发notifyDataSetChanged等操作)时,可见item会重新执行绑定逻辑,重复创建MediaPlayer并重新加载音频,导致正在播放的语音被中断。

解决方案
    1. 抽离全局唯一的MediaPlayer播放管理类,不要在Adapter的绑定逻辑中创建MediaPlayer实例
      避免每次item重绑就新建播放器,统一管理播放状态、当前播放的音频URL、播放进度:
public class AudioPlayerManager {
    private static AudioPlayerManager instance;
    private MediaPlayer mediaPlayer;
    private String currentPlayingUrl;
    private int currentPlayProgress;
    private Handler handler;
    private Runnable progressRunnable;

    private AudioPlayerManager() {
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        handler = new Handler(Looper.getMainLooper());
    }

    public static synchronized AudioPlayerManager getInstance() {
        if (instance == null) {
            instance = new AudioPlayerManager();
        }
        return instance;
    }

    // 对外暴露的播放方法
    public void play(String url, OnPlayStateChangeListener listener) {
        // 如果要播放的是当前正在播放的音频,直接回调状态即可,不用重新加载
        if (url.equals(currentPlayingUrl)) {
            if (mediaPlayer.isPlaying()) {
                pause();
                listener.onPause(currentPlayProgress);
            } else {
                resume();
                listener.onPlay(mediaPlayer.getDuration(), currentPlayProgress);
            }
            return;
        }
        // 播放新音频,先释放之前的资源
        release();
        currentPlayingUrl = url;
        try {
            mediaPlayer.setDataSource(url);
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(mp -> {
                int totalDuration = mp.getDuration();
                mp.start();
                startProgressUpdate(listener);
                listener.onPlay(totalDuration, 0);
            });
            mediaPlayer.setOnCompletionListener(mp -> {
                stopProgressUpdate();
                currentPlayProgress = 0;
                listener.onComplete();
                currentPlayingUrl = null;
            });
            mediaPlayer.setOnBufferingUpdateListener((mp, percent) -> {
                listener.onBufferingUpdate(percent);
            });
        } catch (IOException e) {
            e.printStackTrace();
            listener.onError(e);
        }
    }

    private void resume() {
        mediaPlayer.seekTo(currentPlayProgress);
        mediaPlayer.start();
        startProgressUpdate(null);
    }

    private void pause() {
        mediaPlayer.pause();
        stopProgressUpdate();
        currentPlayProgress = mediaPlayer.getCurrentPosition();
    }

    private void startProgressUpdate(OnPlayStateChangeListener listener) {
        progressRunnable = () -> {
            currentPlayProgress = mediaPlayer.getCurrentPosition();
            if (listener != null) {
                listener.onProgressUpdate(currentPlayProgress, mediaPlayer.getDuration());
            }
            handler.postDelayed(progressRunnable, 1000);
        };
        handler.post(progressRunnable);
    }

    private void stopProgressUpdate() {
        if (progressRunnable != null) {
            handler.removeCallbacks(progressRunnable);
        }
    }

    public void release() {
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.stop();
        }
        mediaPlayer.reset();
        stopProgressUpdate();
        currentPlayingUrl = null;
        currentPlayProgress = 0;
    }

    public String getCurrentPlayingUrl() {
        return currentPlayingUrl;
    }

    public int getCurrentPlayProgress() {
        return currentPlayProgress;
    }
    
    public int getDuration() {
        return mediaPlayer.getDuration();
    }

    public boolean isPlaying(String url) {
        return url.equals(currentPlayingUrl) && mediaPlayer.isPlaying();
    }
    
    public void seekTo(int position) {
        if (mediaPlayer != null && currentPlayingUrl != null) {
            mediaPlayer.seekTo(position);
            currentPlayProgress = position;
        }
    }

    // 状态回调接口
    public interface OnPlayStateChangeListener {
        void onPlay(int totalDuration, int progress);
        void onPause(int progress);
        void onComplete();
        void onProgressUpdate(int progress, int totalDuration);
        void onBufferingUpdate(int percent);
        void onError(Exception e);
    }
}
    1. 修改Adapter的item绑定逻辑,只做UI和播放状态的绑定,不处理播放器初始化
// onBindViewHolder中只做UI绑定
String audioUrl = finalUrlToLoad[1];
AudioPlayerManager playerManager = AudioPlayerManager.getInstance();

// 先根据当前播放状态初始化UI
if (audioUrl.equals(playerManager.getCurrentPlayingUrl())) {
    binding.loadingAudio.setVisibility(GONE);
    binding.playPauseAudio.setVisibility(VISIBLE);
    binding.audioSeekbar.setMax(playerManager.getDuration());
    binding.audioSeekbar.setProgress(playerManager.getCurrentPlayProgress());
    binding.totalDurationAudio.setText(createTimeLabel(playerManager.getDuration()));
    if (playerManager.isPlaying(audioUrl)) {
        binding.playPauseAudio.setImageResource(R.drawable.play_to_pause);
    } else {
        binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
    }
} else {
    binding.audioSeekbar.setProgress(0);
    binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
    binding.loadingAudio.setVisibility(GONE);
    binding.playPauseAudio.setVisibility(VISIBLE);
}

// 播放按钮点击逻辑
binding.playPauseAudio.setOnClickListener(view -> {
    playerManager.play(audioUrl, new AudioPlayerManager.OnPlayStateChangeListener() {
        @Override
        public void onPlay(int totalDuration, int progress) {
            binding.totalDurationAudio.setText(createTimeLabel(totalDuration));
            binding.audioSeekbar.setMax(totalDuration);
            binding.playPauseAudio.setImageResource(R.drawable.play_to_pause);
            // 启动动画逻辑和之前一致
            Drawable drawable = binding.playPauseAudio.getDrawable();
            if( drawable instanceof AnimatedVectorDrawable) {
                ((AnimatedVectorDrawable) drawable).start();
            }
        }

        @Override
        public void onPause(int progress) {
            binding.audioSeekbar.setProgress(progress);
            binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
            Drawable drawable = binding.playPauseAudio.getDrawable();
            if( drawable instanceof AnimatedVectorDrawable) {
                ((AnimatedVectorDrawable) drawable).start();
            }
        }

        @Override
        public void onComplete() {
            binding.audioSeekbar.setProgress(0);
            binding.playPauseAudio.setImageResource(R.drawable.pause_to_play);
        }

        @Override
        public void onProgressUpdate(int progress, int totalDuration) {
            binding.audioSeekbar.setProgress(progress);
            binding.totalDurationAudio.setText(createTimeLabel(totalDuration));
        }

        @Override
        public void onBufferingUpdate(int percent) {
            binding.audioSeekbar.setSecondaryProgress(percent * binding.audioSeekbar.getMax() / 100);
        }

        @Override
        public void onError(Exception e) {
            // 自定义错误处理逻辑
        }
    });
});

// 拖拽进度条逻辑
binding.audioSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
        if (b && audioUrl.equals(playerManager.getCurrentPlayingUrl())) {
            playerManager.seekTo(i);
            seekBar.setProgress(i);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }
});
    1. 改用DiffUtil实现列表增量更新
      不要调用notifyDataSetChanged()刷新整个列表,新消息插入时使用DiffUtil计算新旧列表差异,只更新发生变化的item,避免正在播放的item被重新绑定:
// 示例DiffUtil Callback实现
public class ChatDiffCallback extends DiffUtil.Callback {
    private List<ChatMessage> oldList;
    private List<ChatMessage> newList;

    public ChatDiffCallback(List<ChatMessage> oldList, List<ChatMessage> newList) {
        this.oldList = oldList;
        this.newList = newList;
    }

    @Override
    public int getOldListSize() {
        return oldList.size();
    }

    @Override
    public int getNewListSize() {
        return newList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldList.get(oldItemPosition).getMessageId().equals(newList.get(newItemPosition).getMessageId());
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
    }
}

// 列表更新时调用
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ChatDiffCallback(oldList, newList));
diffResult.dispatchUpdatesTo(adapter);
adapter.setData(newList);
    1. 可选优化:添加音频文件缓存
      已经加载过的音频文件缓存到本地,下次播放同URL音频时直接读取本地文件,减少重复网络请求,进一步提升体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 18:45:03