RecyclerView:检测到不一致。无效的项目位置

Posted

技术标签:

【中文标题】RecyclerView:检测到不一致。无效的项目位置【英文标题】:RecyclerView: Inconsistency detected. Invalid item position 【发布时间】:2015-07-25 02:12:20 【问题描述】:

我们的 QA 检测到一个错误:在旋转 android 设备 (Droid Turbo) 时,发生了以下 RecyclerView 相关的崩溃

java.lang.IndexOutOfBoundsException: 检测到不一致。无效的项目位置 2(offset:2).state:3

对我来说,这看起来像是 RecyclerView 内部的一个内部错误,因为我想不出这是由我们的代码直接引起的...

有人遇到过这个问题吗?

解决办法是什么?

一个残酷的解决方法可能是在异常发生时捕获它并从头开始重新创建 RecyclverView 实例,以避免陷入损坏状态。

但是,如果可能的话,我想更好地了解问题(并可能从源头上解决它),而不是掩盖它。

这个bug不容易复现,但一旦发生就致命。

完整的堆栈跟踪:

W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
    E/AndroidRuntime( 7546): FATAL EXCEPTION: main
    E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
    E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
    E/AndroidRuntime( 7546):    at andro

【问题讨论】:

一个问题:您的复制品的一致性如何?我知道这是谷歌代码here 和here 中的错误。但这可以避免。那么,每次轮换都会发生这种情况吗? 是的,我认为您最好的选择是在轮换期间不允许更改列表视图。 如果您可以轻松复制,我建议您在所有调用 'notify*' 之前打印 'getItemCount' 的值......您可能会发现您的项目数与您的假设不符。 要记住的规则是:“总是在更改数据集后立即通知...(),并确保从同一个线程调用它”。 如果您拨打了adapter.setHasStableIds(),请删除此电话。它对我有用。 【参考方案1】:

在通知之前删除布局管理器的所有视图。喜欢:

myLayoutmanager.removeAllViews();

【讨论】:

它有效。我在滚动加载和标签更改时遇到问题。【参考方案2】:

将 RecyclerView 更新到最新的 1.2.1 版本。 Aaand魔术发生了!不能再破坏我的应用程序了:) 所以..尝试用你的gradle文件中的最新版本替换旧版本号

dependencies 
    ...
    // RecyclerView
    def recyclerview_version = "1.2.1"
    implementation "androidx.recyclerview:recyclerview:$recyclerview_version"

【讨论】:

【参考方案3】:

我发现 环境 mRecycler.setLayoutFrozen(true); 在 swipeContainer 的 onRefresh 方法中。

为我解决了问题。

swipeContainer.setOnRefreshListener(new   SwipeRefreshLayout.OnRefreshListener() 
        @Override
        public void onRefresh() 
            orderlistRecycler.setLayoutFrozen(true);
            loadData(false);

        
    );

【讨论】:

【参考方案4】:

这是一个非常讨厌的错误。

为了处理我的项目点击,我使用了RecyclerView.OnItemTouchListener 的实现,类似于this question 中的解决方案。

在多次刷新RecyclerView 的数据源并单击一个项目后,这个IndexOutOfBoundsException 会使我的应用程序崩溃。当一个项目被点击时,RecyclerView 在内部寻找正确的底层视图并返回它的位置。查看源代码,我看到有一些TasksThreads 计划。简而言之,基本上这只是一些非法状态,两个数据源混合在一起并且不同步,整个事情变得疯狂。

基于此,我删除了我对RecyclerView.OnItemTouchListener 的实现,只是自己点击了AdapterViewHolder

public void onBindViewHolder (final BaseContentView holder, final int position) 

    holder.itemView.setOnClickListener(new OnClickListener() 

      @Override
      public void onClick (View view) 

        // do whatever you like here
      
    );


这可能不是最好的解决方案,但目前不会崩溃。希望这可以为您节省一些时间:)。

【讨论】:

每次调用 onBind 时创建一个新对象会导致大量对象被垃圾回收,用户可能会遇到冻结。【参考方案5】:

add_location.removeAllViews();

            for (int i=0;i<arrayList.size();i++)
            
                add_location.addView(new HolderDropoff(AddDropOffActivtity.this,add_location,arrayList,AddDropOffActivtity.this,this));
            
            add_location.getAdapter().notifyDataSetChanged();

【讨论】:

先移除视图,然后添加占位符视图,然后刷新适配器【参考方案6】:

对不起,迟到但完美的解决方案: 当您尝试删除特定项目时,只需调用 notifydatasetchange() 并在 bindviewholder 中获取这些项目并删除该项目并再次添加到列表的最后一个,然后检查列表位置,如果这是最后一个索引,然后删除项目。 基本上,当您尝试从中心移除项目时,问题就来了。如果您从最后一个索引中删除项目,则没有更多的回收,并且您的适配器计数是 mantine(这是临界点崩溃来这里)并且崩溃解决了下面的代码 sn-p。

 holder.itemLayout.setVisibility( View.GONE );//to hide temprory it show like you have removed item

        Model current = list.get( position );
        list.remove( current );
        list.add( list.size(), current );//add agine to last index
        if(position==list.size()-1)// remove from last index
             list.remove( position );
        

【讨论】:

【参考方案7】:

回复很晚,但这可能对使用该功能的人有所帮助。

确保您的 onStop 或 onPause 方法没有清除您的任何列表

【讨论】:

【参考方案8】:

当我没有意识到我确实同时使用不同的线程调用了两次时,问题发生在我身上。

notifyDataSetChanged

一个来自sqlite加载函数一个来自调用函数后

【讨论】:

【参考方案9】:

我设法解决此问题的一种方法(在具有架构组件的 Kotlin 应用程序中)是在我从存储库中获取数据后设置 if (recyclerView.adapter == null) recyclerView.adapter = MyAdapter(datasource)。显然,它可能与存储库中挂起函数的异步问题有关,因为当我在启动活动时第一次获取数据时,它会调用 REST API,因为数据库中没有数据,一切都很顺利,但之后,无法再次进行相同的查询,导致崩溃。

【讨论】:

【参考方案10】:

就我而言,我遇到了这个异常

java.lang.IndexOutOfBoundsException:检测到不一致。无效的视图支架适配器 positionViewHolder

以上答案都不适用于我的情况。因为我正在更新/更改适配器中的现有项目,但我使用了

myAdapter.notifyItemInserted(position)

相反,我应该使用这个

myAdapter.notifyItemChanged(position)

注意:当我们插入一个项目时我们应该使用notifyItemInserted(),当我们在适配器中更新一个项目时我们应该使用notifyItemChanged()

【讨论】:

这很奇怪。我也曾经混淆过这两种方法,但并没有导致异常。它有时无法正确更新列表。【参考方案11】:

大多数答案是禁用动画或创建新的数据副本,在我看来这太过分了。他们确实有效,但没有解决问题的根本原因。就我而言,这是由于以错误的方式使用 DiffUtils。在areItemsTheSame(old, new) 方法中,我没有正确实现相等性检查,这反过来又导致notifyItemRangeInserted() 通知适配器,即使新列表中有相同数量的项目。因此,请密切注意如何实现 DiffUtils 回调!

【讨论】:

怎么可能?你是用 Java 编写的(不是带有数据类的 Kotlin)吗? @CoolMind 我使用的是 Kotlin 数据类,当您在数据类中有另一个对象作为属性时,数据类的相等性检查不会递归调用相等性检查【参考方案12】:

如果有人遇到同样的问题,我在我的适配器中覆盖了这两个方法,效果很好。

        override fun getItemId(position: Int): Long 
            return position.toLong()
        
    
        override fun getItemViewType(position: Int): Int 
            return position
        

【讨论】:

【参考方案13】:

我刚刚解决了同样的问题。我设置了 RecyclerView.AdaptersetHasStableIds(true) 以避免项目闪烁。

我在getItemId() 中使用了一个可复制字段(我的模型没有id 字段):

override fun getItemId(position: Int): Long 
    // Error-prone due to possibly duplicate name.
    return contacts[position].name.hashCode().toLong()

getItemId() 应该为每个项目返回一个唯一的 id,所以解决方案是这样做:

override fun getItemId(position: Int): Long 
    // Contact's phone is unique, so I use it instead.
    return contacts[position].phone.hashCode().toLong()

【讨论】:

【参考方案14】:

如果 notifyItemChanged(int position),notifyItemInserted(int position) 互相代替使用 当您要更新项目时使用 notifyItemChanged 和要添加项目时使用 notifyItemInserted 会出现此问题

【讨论】:

【参考方案15】:

如果您有多个回收器视图共享同一个适配器,则会弹出此错误,因此为避免此错误,请为这些回收器视图创建单独的适配器。 这使得回收器视图具有非共享池,从而在不需要时阻止适配器填充它们。

【讨论】:

【参考方案16】:

我解决了这个问题,当它获得新数据时,一项一项添加项目。我在适配器内部使用这个函数。

public void add(Data item) 
        if(!params.contains(item)
           params.add(item);
           notifyItemInserted(getItemCount() - 1);
        
    

【讨论】:

【参考方案17】:

就我而言,这解决了我的问题

rv.setAdapter(null);
rv.setItemAnimator(null);

PS:当我在我的 recyclerview 适配器中进行搜索过滤器时出现问题

【讨论】:

以上是关于RecyclerView:检测到不一致。无效的项目位置的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView java.lang.IndexOutOfBoundsException:检测到不一致。视图支架适配器位置无效。由于更改 getItemCount()

RecyclerView 和 java.lang.IndexOutOfBoundsException:检测到不一致。三星设备中的无效视图支架适配器 positionViewHolder

回收商视图:检测到不一致。无效的视图支架适配器 positionViewHolder

RecyclerView:检测到 IndexOutOfBoundsException 不一致。无效的项目位置

检测到不一致的无效视图持有者适配器错误

Gradle:在非 Android 项目上检测到不支持的模块