“废弃或附加的视图可能无法回收”时,RecyclerView崩溃

时间:2014-10-21 01:45:35

标签: android android-recyclerview

我使用RecyclerView使用StaggeredGridLayoutManager从Android网站获取的简单实现,我不断收到此错误,导致我的应用崩溃:

java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true
            at android.support.v7.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:3501)
            at android.support.v7.widget.RecyclerView$LayoutManager.scrapOrRecycleView(RecyclerView.java:5355)
            at android.support.v7.widget.RecyclerView$LayoutManager.detachAndScrapAttachedViews(RecyclerView.java:5340)
            at android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.java:572)
            at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1918)
            at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2155)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1021)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:502)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1663)
            at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1521)
            at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
            at android.view.View.layout(View.java:14008)
            at android.view.ViewGroup.layout(ViewGroup.java:4373)
            at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1892)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1711)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4351)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
            at android.view.Choreographer.doCallbacks(Choreographer.java:562)
            at android.view.Choreographer.doFrame(Choreographer.java:532)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
            at android.os.Handler.handleCallback(Handler.java:725)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5041)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
            at dalvik.system.NativeStart.main(Native Method)  

简单来说,我的意思是它与this page on their website采用相同的实现,唯一的区别是我的网格项的布局是ImageView和几个TextView,所以我不打算重新发布我的代码。

是否有其他人收到此错误并知道如何处理?

28 个答案:

答案 0 :(得分:176)

如果您的XML中android:animateLayoutChanges设置为true并且您在Java代码中的RecyclerView适配器上调用notifyDataSetChanged(),则会导致此错误。

因此,请避免将android:animateLayoutChanges与RecyclerViews一起使用。

答案 1 :(得分:47)

我也必须处理此次崩溃,而在我的情况下,它与android:animateLayoutChanges无关。

我们正在构建的RecyclerView中包含多种类型的视图,其中一些视图中包含EditText个视图。过了一会儿,我们把问题固定在与焦点有关的问题上。这个错误发生在回收EditText时,其中一个是重点。

当然,我们尝试在将新数据绑定到循环播放的视图时清除焦点但在android:focusableInTouchMode="true"上设置RecycleView之前不起作用。实际上,这是此问题最终需要消除的唯一变化。

答案 2 :(得分:23)

我从布局属性中删除了android:animateLayoutChanges,问题已解决。

答案 3 :(得分:14)

在任何人都可以面对此问题的原因中,请检查您是否已将属性android:animateLayoutChanges="true"设置为RecyclerView。这将导致回收和重新附加RecyclerView的项目失败。删除它并将属性分配给RecyclerView的父容器,例如LinearLayout / RelativeLayout,您应该会看到问题消失。

答案 4 :(得分:10)

我花了两天但是无法解决这个问题,最后,我不得不禁用项目预取。

设置布局管理器时,您只需调用

即可

mGridLayoutManager.setItemPrefetchEnabled(false);

这让我的错误消失了。希望它对某人有用。

答案 5 :(得分:8)

使用slimfit粘贴标题时遇到此错误。这是因为错误的第一个位置造成的。我得到了答案here

public void onBindViewHolder(MainViewHolder holder, int position) {

  final View itemView = holder.itemView;
  final LayoutManager.LayoutParams params = LayoutManager.LayoutParams.from(itemView.getLayoutParams());

  params.setSlm(LinearSLM.ID);
  params.width = ViewGroup.LayoutParams.MATCH_PARENT;
  params.setFirstPosition(item.mSectionFirstPosition);
  itemView.setLayoutParams(params);

}

确保为mSectionFirstPosition

传递正确的值

答案 6 :(得分:7)

今天早上我遇到了这个问题,但我没有遇到与上述相同的原因。

通过调试我发现我的ViewHolder中的项目视图有mParent并且它不是空的,在正常情况下它应该是无(这就是日志所说的'&# 34;附加视图可能无法回收",我认为这意味着如果子视图已经附加到父级,那么在回收时会有一些原因导致失败。)

但是我每次手动都没有附加子视图。当我尝试在ViewHolder中对子视图进行充气时,我发现它已经完成了,例如:

layoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

最后一个参数attachToRoot应为false。

我将其更改为false后,我解决了问题。

顺便说一句,我只看到当我将支持库升级到最新版本25.0.0时发生此崩溃。在我使用版本23.4.0之前,我没有看到这个问题发生。我想最新的支持库应该有一些变化。

希望得到这个帮助。

答案 7 :(得分:6)

在我的情况下,它发生的原因是我在尝试调整RecyclerView的大小时运行了Transition,因为软件键盘即将显示。

我使用Transition

修复了我Transition.excludeTarget(R.id.recyclerview, true);中的RecyclerView

答案 8 :(得分:5)

RecyclerView上滚动时,我也遇到了同样的错误:然后我在布局文件中删除了animateLayoutChanges="true"的{​​{1}},然后一切正常。

答案 9 :(得分:5)

每当我在RecyclerView的布局文件中有animateLayoutChanges =“ true”时,我也都会收到此错误。 删除该属性,错误将消失!

答案 10 :(得分:4)

虽然在我的情况下它正在从修复崩溃的recyclerView中删除animateOnLayoutChange,但我仍然需要能够在viewHolder中设置布局更改的动画。为了实现这一点,LinearLayout' in the view holder needs the animateOnLayoutChange'要真实,但我需要notifyItemChanged到适配器。然后,这允许两个layoutTransition动画启动(用于展开和折叠viewHolder),并且还避免了报废的异常。所以,是的,避免将animateOnLayoutChange放在recylcerView上,并使用各种notify方法启用视图大小更改的默认动画。

答案 11 :(得分:3)

我通过删除parent.addView()

中的onCreateViewHolder来解决此问题

这是我的代码

public MyViewHolder onCreateViewwHolder(ViewGroup parent, int viewType)  {
    Button addButton = new Button(context);
    //parent.addView(addButton);
    return new MyViewHolder(addButton);
}

android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()处的功能检查我的按钮是否已经是父母。如果我们向父级添加按钮,则会将RecyclerView分配给其mParent变量。

答案 12 :(得分:2)

我通过调用

解决了这个问题
setHasStableIds(true);

在适配器的构造函数中并覆盖适配器中的getItemId

@Override
public long getItemId(int position) {
    return position;
}

答案 13 :(得分:1)

当我在ViewHolder中为RecyclerView适配器使用自定义对象时,我发现这种情况发生了。

为了解决这个问题,我清除了自定义对象,在我的情况下是适配器onViewRecycled(ViewHolder holder)中的一个计时器,如下所示:

    public void onViewRecycled(ViewHolder holder) {
        if(holder instanceof  EntityViewHolder) {
            if(((EntityViewHolder)holder).timer != null) {
                ((EntityViewHolder) holder).timer.cancel();
            }
        }
        super.onViewRecycled(holder);
    }

这解决了这个问题。

答案 14 :(得分:1)

此异常不是

的原因
  

机器人:animateLayoutChanges

  

机器人:focusableInTouchMode

这个最终正确的答案只是因为你设置了错误的LayoutParams

    nameLP = new LinearLayout.LayoutParams(context.getResources().getDisplayMetrics().widthPixels, LinearLayout.LayoutParams.WRAP_CONTENT);
    nameLP2 = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT);

nameLP没问题。 名称LP2发生崩溃.bug就在这里。

我尝试了本页面的所有答案。相信我。

答案 15 :(得分:1)

从recycleview中删除android:animateLayoutChanges="true"或设置android:animateLayoutChanges="false"

答案 16 :(得分:1)

就我而言,我在使用recyclerView顶部添加视图之前使用了TransitionManager.beginDelayedTransition()。我删除了TransitionManager.beginDelayedTransition()并且没有崩溃。

答案 17 :(得分:1)

1,remove:从列表中删除数据。

2,notifyDataSetChanged:notifyDataSetChanged();

3,notifyItemRemoved:显示动画。

4,notifyItemRangeChanged:范围视图大小并重绘viewHolders(onBindViewHolder methods)

答案 18 :(得分:1)

    /**
     * Informs the recycler whether this item can be recycled. Views which are not
     * recyclable will not be reused for other items until setIsRecyclable() is
     * later set to true. Calls to setIsRecyclable() should always be paired (one
     * call to setIsRecyclabe(false) should always be matched with a later call to
     * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
     * reference-counted.
     *
     * @param recyclable Whether this item is available to be recycled. Default value
     * is true.
     *
     * @see #isRecyclable()
     */
    public final void setIsRecyclable(boolean recyclable) {
        mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
        if (mIsRecyclableCount < 0) {
            mIsRecyclableCount = 0;
            if (DEBUG) {
                throw new RuntimeException("isRecyclable decremented below 0: " +
                        "unmatched pair of setIsRecyable() calls for " + this);
            }
            Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " +
                    "unmatched pair of setIsRecyable() calls for " + this);
        } else if (!recyclable && mIsRecyclableCount == 1) {
            mFlags |= FLAG_NOT_RECYCLABLE;
        } else if (recyclable && mIsRecyclableCount == 0) {
            mFl`enter code here`ags &= ~FLAG_NOT_RECYCLABLE;
        }
        if (DEBUG) {
            Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
        }
    }

答案 19 :(得分:0)

我遇到的一个特殊情况是,我在适配器中有一个视图成员,我懒得实例化一个不需要使用循环视图的视图。

这也违反了回收视图原则,在这种情况下,您可以存储对视图的引用。我在下面给出一个简单的例子:

// typically we would do this in a grid view adapter:
View v;
// ...
if(v = null){
v = LayoutInflater.inflate ...;
}

// Now with recycle view there is NO need to store a reference to View
// and lazy instantiate. So get rid of your View v member

答案 20 :(得分:0)

解决方法解决方案如果Exception的原因是itemView具有父级。 在代码中,您有notifyItemRemoved(position),从RecyclerView中删除itemView:

View itemView = mRecyclerView.getLayoutManager().findViewByPosition(position);
if (itemView != null && itemView.getParent() != null) {
    ((ViewGroup) itemView.getParent()).removeView(itemView);
}
notifyItemRemoved(position);

答案 21 :(得分:0)

在我的情况下,问题是因为此方法public long getItemId(int position)的执行不正确(从RecyclerView.Adapter方法覆盖)。

旧代码将为同一个项目获取两个不同的ID(在我的情况下,它是页脚项目),在修复实现后问题已经消失。

答案 22 :(得分:0)

对我来说,在更高级别的ViewGroup上由LayoutTransition引起的同样的错误。

答案 23 :(得分:0)

请允许我为此类问题添加另一种可能的解决方法。对于RecyclerView中的粘性标题,superSlim库存在同样的问题。我使用MatrixCursor将数据设置为RecyclerViewCursorAdapter。出现此问题的原因是所有标头的ID列等于0。希望这可以帮助某人节省几天的调试时间。

答案 24 :(得分:0)

我遇到了这个问题,因为我通过计算数据相等性和哈希码覆盖了equals()的{​​{1}}的{​​{1}}的{​​{1}}和hashcode()方法,因此回收逻辑不起作用并崩溃了,我只删除了覆盖并修复了。

答案 25 :(得分:0)

我使用com.squareup.picasso.RequestCreator

public void into(android.widget.ImageView target,
             Callback callback)

从Internet下载图片后动态调整ImageView的大小,并保存调整大小的宽度和高度以保留视图大小。我得到了这个例外,因为我将LayoutParams保存在Map中,在我的onBindViewHolder中,我检索了它并直接将其设置为我的ImageView。我通过使用ImmutablePair<Integer, Integer>来解决这个问题,只存储ImageView的大小而不是很多其他状态,并使用以下代码来恢复它。

ViewGroup.LayoutParams params = image.getLayoutParams();
params.width = widthAndHeight.getLeft();
params.height = widthAndHeight.getRight();
image.setLayoutParams(params);

答案 26 :(得分:0)

调用此异常有很多原因。在我的情况下,这是由于运行动画,这就是为什么视图仍附加且无法删除到视图的原因。只有在动画制作完成后,视图才能被删除和回收。

有两种类型的动画可能会影响recyclerview的回收。

1)是RecyclerView.ItemAnimator-这应该不是问题。使用它应该非常安全,因为它可以检查附加视图和已废弃视图并正确处理回收。

2)android:animateLayoutChanges="true"TransitionManager.beginDelayedTransition()或TransitionManager.go(),等等。-这些动画独立运行并抓住要进行动画处理的项目。这导致视图被强制附加到动画完成为止。 recyclerview不了解这些动画,因为它不在其范围内。因此recyclerview可能会尝试回收某个项目,认为它可以正确回收,但是问题是这些API仍会保留视图,直到动画结束为止。

如果您使用的是android:animateLayoutChanges="true"TransitionManager.beginDelayedTransition()或TransitionManager.go()等,只需从动画中删除RecyclerView及其子元素即可。

您只需按住Transition并致电

Transition.excludeChildren(yourRecyclerView, true)
Transition.excludeTarget(yourRecyclerView, true)

注意:

请注意,重要的是使用Transition.excludeChildren()从动画中排除所有Recyclerview个孩子,而不仅仅是Recyclerview本身。

答案 27 :(得分:0)

请注意,如果您使用的是Kotlin,请不要将np.stack()表示为ViewHolder

(感谢我的同事注意这一点)