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
相关产品推荐
相关产品推荐

