RecyclerView中SimpleExoPlayer调用setPlayWhenReady死线程异常求助
问题分析
这个IllegalStateException本质是ExoPlayer关联的后台线程已经销毁(播放器已释放),但仍有消息试图通过Handler发送到该线程。在RecyclerView场景下,核心问题出在View复用导致的播放器状态不同步——当View被快速滑动回收/复用时,播放器的释放、初始化操作可能出现时序问题,比如:
- View被复用前,旧的播放器还没完全释放,新的播放器已经创建;
- 播放器释放后,之前注册的Listener仍在回调,尝试操作已销毁的播放器;
- 点击播放按钮时,播放器已经被释放,但代码没做状态检查就调用
setPlayWhenReady。
解决方案
下面是针对性的修复方案,按优先级排序:
1. 给播放器状态加原子标记,避免无效操作
在自定义View中添加一个标记,跟踪播放器是否已释放,所有播放器操作前先检查标记,从根源避免调用已释放的播放器方法:
public class MyCustomView extends View { private SimpleExoPlayer player; private boolean isPlayerReleased = false; // 新增状态标记 private Player.EventListener playerEventListener; private DataSource.Factory dataSourceFactory; private TrackSelector trackSelector; // ... 其他成员变量 ... // 初始化播放器的封装方法 private void initPlayer(String videoUrl) { isPlayerReleased = false; DefaultLoadControl loadControl = new DefaultLoadControl.Builder() .setBufferDurationsMs(1100, 2000, 1000, 1000).createDefaultLoadControl(); player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, loadControl); playerView.setUseController(false); playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH); playerView.setPlayer(player); // 初始化Listener playerEventListener = new PlayerEventListener(this); player.addListener(playerEventListener); Uri uri = Uri.parse(videoUrl); MediaSource audioSource = new ExtractorMediaSource.Factory(dataSourceFactory) .createMediaSource(uri); player.prepare(audioSource); } // 释放播放器的方法 private void releasePlayer() { if (player != null) { isPlayerReleased = true; player.removeListener(playerEventListener); // 先移除Listener player.release(); player = null; videoPlay.setVisibility(View.VISIBLE); postImage.setVisibility(View.VISIBLE); } } // 播放按钮点击事件修改 videoPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isPlayerReleased || player == null) { // 播放器已释放,重新初始化 initPlayer(videoUrl); player.setPlayWhenReady(true); updateViewVisibility(true); // 封装视图显示逻辑 return; } updateViewVisibility(true); try { player.setPlayWhenReady(true); } catch (IllegalStateException e) { e.printStackTrace(); // 捕获异常后重试初始化 initPlayer(videoUrl); player.setPlayWhenReady(true); } } }); // 封装视图显示/隐藏逻辑 private void updateViewVisibility(boolean isPlaying) { videoPlay.setVisibility(isPlaying ? View.GONE : View.VISIBLE); postImage.setVisibility(isPlaying ? View.GONE : View.VISIBLE); playerView.setVisibility(isPlaying ? View.VISIBLE : View.GONE); } }
2. 利用RecyclerView的回调确保播放器在复用前被释放
RecyclerView的onViewRecycled回调会在View被回收准备复用时触发,在这里主动调用自定义View的释放方法,比仅依赖onDetachedFromWindow更可靠:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { // ... 其他代码 ... @Override public void onViewRecycled(@NonNull ViewHolder holder) { super.onViewRecycled(holder); // 主动释放自定义View中的播放器 holder.myCustomView.releasePlayer(); } }
同时保留onDetachedFromWindow的释放逻辑,形成双重保障:
public class MyCustomView extends View { // ... 其他代码 ... @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); releasePlayer(); } }
3. 优化Player.EventListener,避免内存泄漏和无效回调
避免使用匿名内部类作为Listener,改用静态内部类+弱引用,防止Listener持有View引用导致内存泄漏,同时在回调中检查播放器状态:
public class MyCustomView extends View { // ... 其他代码 ... // 静态内部类+弱引用的Listener private static class PlayerEventListener implements Player.EventListener { private WeakReference<MyCustomView> viewRef; public PlayerEventListener(MyCustomView view) { this.viewRef = new WeakReference<>(view); } @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { MyCustomView view = viewRef.get(); if (view == null || view.isPlayerReleased || view.player == null) { return; // View已回收或播放器已释放,直接返回 } if (playbackState == Player.STATE_ENDED) { view.updateViewVisibility(false); try { view.player.setPlayWhenReady(false); view.player.seekTo(0); } catch (IllegalStateException e) { e.printStackTrace(); } } } } }
4. 添加重试逻辑(可选)
如果仍有偶发的异常,可以在捕获异常后添加延迟重试逻辑,确保播放器状态稳定后再操作:
// 在播放按钮的try-catch中添加重试 try { player.setPlayWhenReady(true); } catch (IllegalStateException e) { e.printStackTrace(); // 延迟500ms重试初始化 postDelayed(() -> { if (!isPlayerReleased) { initPlayer(videoUrl); player.setPlayWhenReady(true); } }, 500); }
总结
核心思路是确保播放器状态的一致性:所有操作前检查状态,在View回收/复用、脱离窗口时及时释放播放器,同时避免Listener的无效回调和内存泄漏。通过这些优化,基本可以解决这个偶发的Handler异常问题。
内容的提问来源于stack exchange,提问作者Andrei F
相关产品推荐
相关产品推荐

