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

加载新Fragment或屏幕旋转时ExoPlayer多实例重复播放问题解决方案咨询

解决ExoPlayer切换Fragment/旋转屏幕时多实例播放混乱的问题

你的问题核心在于每次Fragment重建(切换页面、屏幕旋转)都会创建新的ExoPlayer实例,而旧实例没有被正确释放,导致多个音频同时播放。这是因为你把Player的创建逻辑绑定在了Fragment的生命周期里,而Fragment的生命周期会随页面切换、配置变更重启,下面是具体的解决方案:

一、核心问题分析

现有代码存在几个关键错误:

  • onStop()方法里错误调用了initializeMediaPlayer(),这会在Fragment停止时新建Player,旧实例没释放直接导致多实例。
  • 每次Fragment重建(比如旋转屏幕)都会触发onCreateView(),进而重新初始化Player,旧实例仍在后台运行。
  • 没有一个全局的、不随Fragment生命周期销毁的Player托管容器。

二、最优解决方案:用ViewModel托管ExoPlayer

ViewModel的生命周期和宿主Activity绑定,不会随Fragment重建、页面切换而销毁,完美解决多实例问题。

1. 创建ExoPlayer的ViewModel类

import androidx.lifecycle.ViewModel;
import com.google.android.exoplayer2.SimpleExoPlayer;
import android.content.Context;
import com.google.android.exoplayer2.MediaItem;

public class PlayerViewModel extends ViewModel {
    private SimpleExoPlayer player;
    private boolean playWhenReady = true;
    private int currentWindow = 0;
    private long playbackPosition = 0;

    // 获取唯一的Player实例
    public SimpleExoPlayer getPlayer(Context context) {
        if (player == null) {
            player = new SimpleExoPlayer.Builder(context).build();
            // 恢复之前的播放状态
            player.setPlayWhenReady(playWhenReady);
            player.seekTo(currentWindow, playbackPosition);
        }
        return player;
    }

    // 保存播放状态
    public void savePlayerState() {
        if (player != null) {
            playWhenReady = player.getPlayWhenReady();
            playbackPosition = player.getCurrentPosition();
            currentWindow = player.getCurrentWindowIndex();
        }
    }

    // 释放Player(在Activity销毁时调用)
    public void releasePlayer() {
        savePlayerState();
        if (player != null) {
            player.release();
            player = null;
        }
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        releasePlayer();
    }
}

2. 修改Fragment代码,从ViewModel获取Player

import androidx.lifecycle.ViewModelProvider;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.ui.PlayerView;
import android.widget.SeekBar;

public class HomeFragment extends Fragment {
    private PlayerViewModel playerViewModel;
    private SimpleExoPlayer player;
    private PlayerView playerView;
    private SeekBar playSeekBar;
    private String revurl = "你的流媒体地址"; // 替换为实际地址

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_home, container, false);
        initializeUIElements(root);
        // 初始化ViewModel(绑定Activity生命周期)
        playerViewModel = new ViewModelProvider(requireActivity()).get(PlayerViewModel.class);
        return root;
    }

    private void initializeUIElements(View root) {
        playSeekBar = root.findViewById(R.id.progressBar1);
        playSeekBar.setMax(100);
        playSeekBar.setVisibility(View.INVISIBLE);
        playerView = root.findViewById(R.id.video_view);
    }

    @Override
    public void onStart() {
        super.onStart();
        if (Util.SDK_INT >= 24) {
            initPlayer();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if ((Util.SDK_INT < 24 || player == null)) {
            initPlayer();
        }
    }

    private void initPlayer() {
        player = playerViewModel.getPlayer(getContext());
        playerView.setPlayer(player);
        // 仅第一次初始化时设置媒体地址,避免重复加载
        if (player.getMediaItemCount() == 0) {
            MediaItem media = MediaItem.fromUri(revurl);
            player.setMediaItem(media);
            player.prepare();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (Util.SDK_INT < 24) {
            playerViewModel.savePlayerState();
            playerView.setPlayer(null); // 断开与View的绑定,避免内存泄漏
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (Util.SDK_INT >= 24) {
            playerViewModel.savePlayerState();
            playerView.setPlayer(null);
        }
    }

    @SuppressLint("InlinedApi")
    private void hideUi() {
        playerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
    }
}

3. 在主Activity中处理全局释放

当Activity真正销毁时,释放ViewModel中的Player实例:

@Override
protected void onDestroy() {
    super.onDestroy();
    PlayerViewModel playerViewModel = new ViewModelProvider(this).get(PlayerViewModel.class);
    playerViewModel.releasePlayer();
}

三、其他注意事项

  • 切换Fragment时:Player实例会保留在ViewModel中,若需要暂停播放,可在Fragment的onPause中调用player.setPlayWhenReady(false)。
  • 屏幕旋转:ViewModel会自动保留Player实例和播放状态,旋转后直接恢复,不会新建实例。
  • 避免重复加载媒体:通过player.getMediaItemCount() == 0判断,确保仅第一次初始化时设置媒体地址。

这样修改后,整个App只会存在一个ExoPlayer实例,彻底解决多实例播放混乱的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 05:03:30