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

Android Studio中Java实现ViewPager+ExoPlayer的问题咨询

解决方案:ViewPager + ExoPlayer 视频播放管理问题修复

核心问题本质

你遇到的所有问题都是因为ExoPlayer实例未和ViewPager的页面复用机制、宿主组件生命周期绑定,导致播放器实例混乱、资源未正确释放。下面是针对性的修复方案:


1. 改造PagerAdapter,精准管理播放器实例

ViewPager会复用页面,不能每次创建新播放器,也不能随意丢弃实例。我们用SparseArray缓存每个位置的播放器,同时跟踪当前播放页面,切换时自动启停。

修改你的Adapter代码,加入以下逻辑:

public class VideoPagerAdapter extends PagerAdapter {
    private Context mContext;
    private List<String> mVideoUrls;
    private SparseArray<SimpleExoPlayer> mPlayerCache = new SparseArray<>();
    private int currentPlayingPos = -1;

    public VideoPagerAdapter(Context context, List<String> videoUrls) {
        mContext = context;
        mVideoUrls = videoUrls;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_video, container, false);
        PlayerView playerView = itemView.findViewById(R.id.player_view);

        // 优先复用缓存的播放器,避免重复创建
        if (mPlayerCache.get(position) == null) {
            SimpleExoPlayer player = new SimpleExoPlayer.Builder(mContext).build();
            playerView.setPlayer(player);
            // 设置播放源
            MediaItem mediaItem = MediaItem.fromUri(mVideoUrls.get(position));
            player.setMediaItem(mediaItem);
            player.prepare();
            mPlayerCache.put(position, player);
        } else {
            playerView.setPlayer(mPlayerCache.get(position));
        }

        container.addView(itemView);
        return itemView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        View itemView = (View) object;
        PlayerView playerView = itemView.findViewById(R.id.player_view);
        SimpleExoPlayer player = mPlayerCache.get(position);

        if (player != null) {
            // 如果是当前播放页,先停止
            if (position == currentPlayingPos) {
                player.stop();
                currentPlayingPos = -1;
            }
            // 解绑PlayerView,防止内存泄漏
            playerView.setPlayer(null);
            // 若页面不会被复用,直接释放资源;否则仅暂停(按需调整)
            // player.release();
            // mPlayerCache.remove(position);
        }
        container.removeView(itemView);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        // 切换页面时,自动停止上一个,播放当前
        if (currentPlayingPos != position) {
            // 停止上一个播放器
            if (currentPlayingPos != -1 && mPlayerCache.get(currentPlayingPos) != null) {
                mPlayerCache.get(currentPlayingPos).pause();
            }
            // 播放当前页面视频
            SimpleExoPlayer currentPlayer = mPlayerCache.get(position);
            if (currentPlayer != null) {
                currentPlayer.play();
                currentPlayingPos = position;
            }
        }
    }

    // 新增:释放所有播放器资源的方法
    public void releaseAllPlayers() {
        for (int i = 0; i < mPlayerCache.size(); i++) {
            int key = mPlayerCache.keyAt(i);
            SimpleExoPlayer player = mPlayerCache.get(key);
            if (player != null) {
                player.stop();
                player.release();
            }
        }
        mPlayerCache.clear();
        currentPlayingPos = -1;
    }

    // 实现getCount、isViewFromObject等基础方法
    @Override
    public int getCount() {
        return mVideoUrls.size();
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == object;
    }
}

2. 绑定宿主Activity生命周期,跳转/后台时停止播放

在承载ViewPager的Activity中,监听生命周期事件,确保离开页面时释放播放器资源:

public class VideoListActivity extends AppCompatActivity {
    private ViewPager mViewPager;
    private VideoPagerAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_list);
        mViewPager = findViewById(R.id.view_pager);
        
        // 替换为你的视频URL列表
        List<String> videoUrls = Arrays.asList("url1", "url2", "url3");
        mAdapter = new VideoPagerAdapter(this, videoUrls);
        mViewPager.setAdapter(mAdapter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 暂停当前播放的视频
        if (mAdapter.currentPlayingPos != -1) {
            SimpleExoPlayer player = mAdapter.mPlayerCache.get(mAdapter.currentPlayingPos);
            if (player != null) player.pause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 销毁时彻底释放所有播放器
        mAdapter.releaseAllPlayers();
    }

    // 跳转到其他Activity前,先暂停播放
    public void gotoOtherActivity(View view) {
        if (mAdapter.currentPlayingPos != -1) {
            SimpleExoPlayer player = mAdapter.mPlayerCache.get(mAdapter.currentPlayingPos);
            if (player != null) player.pause();
        }
        startActivity(new Intent(this, OtherActivity.class));
    }
}

3. 检查Item布局配置

确保你的item_video.xml中PlayerView配置正确,避免不必要的控件干扰:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#000000"
        app:show_buffering="always"
        app:show_shuffle_button="false"
        app:show_subtitle_button="false"
        app:show_time="false" />

</RelativeLayout>

关键优化提示

  • 内存控制:如果视频数量多,建议在destroyItem中仅保留当前页+前后1页的播放器,其余直接释放,避免内存溢出。
  • 滑动体验:快速滑动时可加入延迟播放逻辑,避免频繁启停播放器。
  • 泄漏检测:用LeakCanary工具检查播放器实例是否被正确释放,确保PlayerView在页面销毁时解绑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 05:30:22