当“已回收或附加的视图可能无法被回收”时,RecyclerView崩溃

119

我正在使用从Android网站上借鉴的简单实现RecyclerView,使用StaggeredGridLayoutManager,但是我一直遇到这个导致应用程序崩溃的错误:

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)  
简单来说,我指的是从他们网站上此页面中采取的相同实现,唯一的区别是我的网格项布局是一个ImageView和几个TextView,所以我不会麻烦地重新发布我的代码。 有人遇到这个错误并知道如何处理吗?

你有任何解决方案吗? - Pratik Butani
28个回答

192

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

因此,只需避免在RecyclerView中使用android:animateLayoutChanges即可。


22
那么如何在RecyclerView中使用animateLayoutChanges功能? - Dhrumil Shah - dhuma1981
4
如果通过mRecyclerView.setItemAnimator(new DefaultItemAnimator());设置了项目动画器,则不需要将animateLayoutChanges设置为true。 - Rich Ehmer
RecyclerView 默认使用 DefaultItemAnimator - Jade
我有和你描述的完全一样的问题。 - Ninja Coding
无法找到一种在使用GridLayoutManager的RecyclerView中动画删除列的方法,考虑使用animateLayoutChanges但是也遇到了这个崩溃... - frangulyan
显示剩余3条评论

54

我也遇到了这种崩溃问题,但与android:animateLayoutChanges无关。

我们正在构建的RecyclerView中有多种类型的视图,其中一些包含EditText。最终我们把这个问题归结为焦点相关的。当回收EditText并且其中一个被聚焦时,就会出现此错误。

自然地,我们尝试在绑定新数据到已回收的视图时清除焦点,但这样做没有效果,直到在RecycleView上设置android:focusableInTouchMode="true"。实际上,最终唯一需要做的更改就是这个,以解决这个问题。


2
太棒了,解决了我在使用RecyclerView中的EditTexts时遇到的多个焦点相关问题。谢谢! - Rabie Jradi
1
我在recyclerview中遇到了ACET问题,导致程序崩溃。这篇文章救了我。 - Kai Wang
1
我在项目中没有编辑文本框,但是有复选框。我应该尝试使用android:focusableInTouchMode="true"吗?因为这种情况只在某些设备上偶尔发生,我猜想这与我的问题无关,但崩溃的堆栈跟踪几乎相同。 - Shivansh
这是我的情况,但设置android:focusableInTouchMode="true"对我没有帮助。所以我在onViewDetachedFromWindow回调中清除了焦点。public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } } - artman

26

我从布局属性中移除了android:animateLayoutChanges,问题得到了解决。


当我在我的RV上放置android:animateLayoutChanges时,开始出现这个崩溃。 - Mauker
3
在父容器(相对布局)上设置了这个标志,问题得到了解决。 - 1911z
@1911z,你是说你在父容器上设置了标志,然后将其从那里移除就解决了问题? - RamPrasadBismil

14

如果出现这个问题,可能原因之一是你将android:animateLayoutChanges="true"属性设置到了RecyclerView中。这会导致RecyclerView的回收和重新附加项目操作失败。请将此属性移除,并将其分配到RecyclerView的父容器中,例如LinearLayout/RelativeLayout。这样就应该可以解决问题了。


1
我看到了这个崩溃,即使我已经在RV的父容器上设置了属性。 - RamPrasadBismil
@RamPrasadBismil 请发布您的代码,也许我们可以看一下? - Ram Iyer

12

我花了两天的时间都无法解决这个问题,最后只好禁用项目预取。

当设置布局管理器时,您可以简单地调用

mGridLayoutManager.setItemPrefetchEnabled(false);

对于我来说,这使错误消失了。希望对其他人有用。


对我有用。谢谢。 - Vicky
1
我真的很担心人们采用这个解决方案。禁用此标志会损失很多,而且错误仍然存在于其他地方。 - Felipe Castilhos
1
说实话,它只是让我所有的行都消失了。不过我必须承认,崩溃也消失了 XD。 - Antek

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

但是如果我想要那个过渡呢? - div

8

使用 slimfit 粘性标题时,我遇到了这个错误。原因是设置了错误的第一个位置。我在这里找到了答案。

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值。


欢迎来到StackOverflow。您能否提供一个完整的答案,而不仅仅是一个链接? - slfan
这里的item是什么? - Bugs Happen
这是要在回收视图中显示的列表项。因此,基本上我正在保存每个列表项的部分第一个位置。 - Jaspinder Kaur

8
我今早遇到了这个问题,但我面临的原因与上述不同。通过调试,我发现ViewHolder中的项目视图具有“mParent”,而且它不为null。在正常情况下,它应该是没有父级的(这就是日志所说的,“附加视图可能无法回收利用”的含义,我认为这意味着如果子视图已经附加到父级,则在回收利用时会出现某种故障)。但我并没有每次手动附加子视图。我发现,当我尝试在ViewHolder中填充子视图时,它自动完成了。类似于:
layoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

最后一个参数attachToRoot应该是false。

当我将其更改为false时,问题得到了解决。

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

希望这可以帮助到您。


7

有许多原因导致此异常被称为。在我的情况下,这是由于正在运行的动画造成了视图仍然附加并且无法从视图中移除。只有当动画完成后,视图才能被删除和回收。

有两种类型的动画可能会影响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本身。


谢谢!在我的情况下,TransitionManager.beginDelayedTransition() 是问题的原因。您可以更新您的代码示例,以更详细地说明如何使用 Transition.excludeChildren。您可以像这样实例化一个转换对象:val transition = AutoTransition(),在此对象上调用 excludeChildren(recyclerView, true),并将其作为第二个参数传递给 beginDelayedTransaction() - Danilo Prado

6

我的情况是因为在尝试调整RecyclerView大小时,有一个Transition正在运行,因为软键盘即将显示。

我通过使用 Transition.excludeTarget(R.id.recyclerview,true); 来排除RecyclerView的Transition,从而解决了这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接