这个错误是在 RecyclerView 的Recycler 类中抛出的,具体位置在recycleViewHolderInternal(ViewHolder holder)方法里。当尝试回收一个已附加到父视图(isAttached=true)的 ViewHolder 时,就会触发这个异常。
错误根源
RecyclerView 的回收机制要求:
- 只有未附加到 RecyclerView 的 ViewHolder 才能被回收
- 临时废弃(scrap)状态的 ViewHolder 需先从 scrap 状态移除才能回收
RecyclerView中内部类Recycler的recycleViewHolderInternal()方法对应代码段如下:
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null) + exceptionLabel());
}
常见触发场景
- 异步操作:在 ViewHolder 绑定数据后,异步操作导致视图状态变化,此时 RecyclerView 尝试回收它
- 自定义动画:动画执行期间,ViewHolder 仍被视为附加状态
- 错误的 notify 调用:如在动画或布局过程中调用
notifyDataSetChanged()
解决方向
-
确保在 RecyclerView 执行动画或布局时,不进行数据更新操作
-
使用
adapter.notifyItemRangeChanged()代替notifyDataSetChanged() -
在自定义动画结束后再回收 ViewHolder
-
避免在 ViewHolder 的回调方法中执行耗时操作
这个错误本质是 RecyclerView 的回收策略与视图状态管理之间的冲突,需要在代码中确保视图状态与回收操作的同步性。
这个异常发生于尝试回收仍被附加到父视图的 ViewHolder 时。下面为你提供几种解决办法:
1. 确认数据更新操作的时机
要保证在 RecyclerView 处于稳定状态时才进行数据更新。可以借助post方法来延迟执行可能会引发异常的操作。
java
recyclerView.post(new Runnable() {
@Override
public void run() {
// 执行notify相关操作
adapter.notifyItemRemoved(position);
}
});
2. 防止在动画进行中回收 ViewHolder
在执行自定义动画时,要避免调用notify系列方法。可以在动画结束的回调里进行数据更新。
java
// 自定义ItemAnimator
public class CustomItemAnimator extends DefaultItemAnimator {
@Override
public boolean animateRemove(final ViewHolder holder) {
// 自定义移除动画
ViewCompat.animate(holder.itemView)
.alpha(0)
.setDuration(getRemoveDuration())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束后重置视图状态
resetAnimation(holder);
// 通知动画结束
dispatchRemoveFinished(holder);
// 关键步骤:标记动画已完成
holder.itemView.setAlpha(1);
}
})
.start();
return true;
}
}
3. 优化 ViewHolder 的回收逻辑
在需要手动回收 ViewHolder 的情况下,先将其从父视图中分离。
java
// 在adapter中
public void safelyRecycleViewHolder(ViewHolder holder) {
if (holder.itemView.getParent() != null) {
((RecyclerView) holder.itemView.getParent()).removeView(holder.itemView);
}
recyclerView.getRecycler().recycleViewHolderInternal(holder);
}
4. 处理异步操作
如果在 ViewHolder 中有异步操作,要确保在视图被回收前终止这些操作。
java
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
// 取消异步任务
holder.cancelAsyncTasks();
}
5. 使用适当的 notify 方法
优先使用更精确的notifyItemChanged(),而非notifyDataSetChanged()。
java
// 不要用这个
adapter.notifyDataSetChanged();
// 要用这个
adapter.notifyItemChanged(position);
6. 检查自定义 LayoutManager
若你使用的是自定义 LayoutManager,要确保在onDetachedFromWindow()方法中正确处理视图回收。
java
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
// 释放所有缓存的视图
recycler.clear();
}
7. 避免在 onBindViewHolder 中执行耗时操作
在onBindViewHolder里执行耗时操作可能会使 ViewHolder 长时间处于附加状态。
java
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// 不要在这里执行网络请求等耗时操作
// 应该在ViewModel中处理数据,然后通过LiveData传递到这里
}
总结
这个异常主要是由于视图状态管理和 RecyclerView 回收机制之间存在冲突。解决的关键在于:
-
保证视图在被回收前处于未附加状态
-
防止在动画或异步操作期间回收视图(这就是我这次遇到的问题,没有合理处理ITEM中的动画导致)
-
合理运用 RecyclerView 的回调方法来管理视图状态
如果你的项目使用了 DataBinding 或者 ViewBinding,要确保它们的生命周期与 ViewHolder 的状态保持同步。