首页 文章

RecyclerView:检测到不一致 . 商品位置无效

提问于
浏览
190

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

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 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

30 回答

  • 1

    我解决了这个问题,在获取新数据时逐个添加项目 . 我在适配器内使用此功能 .

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

    你只需要在 OnPostExecute() 清除你的清单,而不是在做 Pull to Refresh

    // Setup refresh listener which triggers new data loading
            swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
    
                    AsyncTask<String,Void,String> task = new get_listings();
                    task.execute(); // clear listing inside onPostExecute
    
                }
            });
    

    我发现在 pull to refresh 期间滚动时会发生这种情况,因为我在 async task 之前清除了列表,导致 java.lang.IndexOutOfBoundsException: Inconsistency detected.

    swipeContainer.setRefreshing(false);
            //TODO : This is very crucial , You need to clear before populating new items 
            listings.clear();
    

    这样你就不会以不一致的方式结束

  • 0

    当您尝试清除列表时可能会发生此问题,如果您要清除数据列表,尤其是当您使用pull to refresh尝试使用布尔标志时,将其初始化为false并在OnRefresh方法中将其设置为true,清除dataList如果flag在将新数据添加到它之前为true,那么将其设为false .

    你的代码可能是这样的

    private boolean pullToRefreshFlag = false ;
     private ArrayList<your object> dataList ;
     private Adapter adapter ;
    
     public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{
    
     private void requestUpdateList() {
    
         if (pullToRefresh) {
            dataList.clear
            pullToRefreshFlag = false;
         }
    
         dataList.addAll(your data);
         adapter.notifyDataSetChanged;
    
    
     @Override
     OnRefresh() {
     PullToRefreshFlag = true
     reqUpdateList() ; 
     }
    
    }
    
  • 0

    我在recyclerView上遇到了同样的问题所以我只是在列表清除后立即通知适配器有关数据集的更改 .

    mList.clear();
    mAdapter.notifyDataSetChanged();
    
    mList.addAll(newData);
    mAdapter.notifyDataSetChanged();
    
  • 10

    要修复此问题,请在更新循环视图之前使用空列表调用notifyDataSetChanged() .

    例如

    //Method for refresh recycle view
    
        if (!hcpArray.isEmpty())
    

    hcpArray.clear(); //更新循环视图的列表

    adapter.notifyDataSetChanged();
    
  • 3

    抱歉,对于迟到但完美的解决方案:当您尝试删除特定项目时,只需调用notifydatasetchange()并在bindviewholder中获取这些项目并删除该项目并再次添加到列表的最后,然后如果这是最后一个索引则删除列表位置然后删除项目 . 基本上问题出在你试图从中心删除项目时 . 如果你从上一个索引中删除项目,那么就没有更多的回收,而且你的adpter计数是mantine(这是关键点崩溃来到这里),崩溃解决了下面的代码片段 .

    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 );
            }
    
  • 1

    我遇到了一个类似的问题,只是想出来了 . 我为测试用例硬编了几个例子,但没有确保他们每个都返回一个唯一的ID,这导致了我的下面崩溃 . 修复ID解决了问题,希望这有助于其他人!

  • 2

    我正在改变背景 ThreadRecyclerView 的数据 . 我和OP一样 Exception . 我在更改数据后添加了这个:

    myRecyclerView.post(new Runnable() {
        @Override
        public void run() {
            myRecyclerAdapter.notifyDataSetChanged();
        }
    });
    

    希望能帮助到你

  • 0

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

    为了处理我的项目点击,我使用_420149的实现类似于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
          }
        });
    
    }
    

    这可能不是最好的解决方案,但现在没有崩溃..希望这会节省你一些时间:) .

  • 0

    在这种情况下,请使用 notifyDataSetChanged() 而不是 notifyItem... .

  • 2

    我以前遇到过同样的问题 . 终于找到了解决方法

    我所做的是通知适配器该项目已删除,然后通知适配器数据集范围已更改

    public void setData(List<Data> dataList) {
          if (this.dataList.size() > 0) {
              notifyItemRangeRemoved(0, dataList.size());
              this.dataList.clear();
          }
          this.dataList.addAll(dataList)
          notifyItemRangeChanged(0, dataList.size());
    
     }
    
  • 2

    我发现设置mRecycler.setLayoutFrozen(true);在swipeContainer的onRefresh方法中 .

    为我解决了这个问题 .

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

    Lint给了我一个关于不一致的建议:我写了(onBindViewHolder()):

    pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            doStuff(position);
                        }
                    });
    

    必须替换为:

    pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            doStuff(pholder.getAdapterPosition());
                        }
                    });
    

    在代码中运行两个代码,然后运行Lint进行完整的解释!

  • 3

    我遇到了同样的情况 . 在清除您的收藏之前,通过添加代码解决了这个问题 .

    mRecyclerView.getRecycledViewPool().clear();

  • 0

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

    myLayoutmanager.removeAllViews();
    
  • 1

    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();
    
  • 5

    就我而言,我正在更新项目并在非UI线程中调用 notifyDataSetChanged . 它大部分时间都有效,但是当很多变化很快发生时,就会崩溃 . 当我做的时候,基本上

    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            changeData();
            notifyDataSetChanged();
        }
    });
    

    然后它停止了崩溃 .

  • 155

    我有类似的问题,但不完全一样 . 在我的情况下,在1点我清理阵列被传递给了recyclerview

    mObjects.clear();
    

    而不是调用notifyDataSetChanged,因为我不希望recyclerview立即清除视图 . 我在AsyncTask中重新填充了mObjects数组 .

  • 1

    我最近使用新的Android架构组件遇到了这个讨厌的堆栈跟踪 . 从本质上讲,我的ViewModel中有一个项目列表,我的Fragment使用LiveData观察这些项目 . 当ViewModel为数据发布新值时,Fragment会更新适配器,传入这些新数据元素并通知适配器已发生更改 .

    不幸的是,在将新数据元素传递给适配器时,我没有考虑到ViewModel和Adapter都指向同一个对象引用这一事实!这意味着如果我更新数据并从ViewModel中调用 postValue() ,那么可以使用一个非常小的窗口来更新数据并且尚未通知适配器!

    我的修复是在传递给适配器时实例化元素的新副本:

    mList = new ArrayList<>(passedList);

    通过这种超级简单的修复,您可以确保在适配器通知之前,适配器数据不会发生变化 .

  • 0

    使用

    notifyDataSetChanged()
    

    代替

    notifyItemRangeInserted(0, YourArrayList.size())
    

    在这种情况下 .

  • 2

    用户滚动时适配器中的列表清除时会发生此错误,这会使项目持有者的位置发生变化,列表和项目上的参数ui丢失,错误发生在下一个 "notifyDataSetChanged" 请求中 .

    固定:

    查看更新列表方法 . 如果你做的事情

    mainList.clear();
    ...
    mainList.add() or mainList.addAll()
    ...
    notifyDataSetChanged();
    
    ===> Error occur
    

    怎么修 . 为缓冲区处理创建新的列表对象,然后再次分配给主列表

    List res = new ArrayList();
    …..
    res.add();  //add item or modify list
    ….
    mainList = res;
    notifyDataSetChanged();
    

    感谢Nhan Cao这个很棒的帮助:)

  • 1

    我有一个(可能)相关的问题 - 使用RecyclerView输入活动的新实例,但是使用较小的适配器会导致我崩溃 .

    RecyclerView.dispatchLayout() 可以在调用 mRecycler.clearOldPositions() 之前尝试从废料中提取物品 . 结果是,它是从公共池中拉出物品,其位置比适配器大小更高 .

    幸运的是,只有在启用了PredictiveAnimations时才会这样做,所以我的解决方案是继承GridLayoutManager(LinearLayoutManager有同样的问题和'fix'),并覆盖supportsPredictiveItemAnimations()以返回false:

    /**
     * No Predictive Animations GridLayoutManager
     */
    private static class NpaGridLayoutManager extends GridLayoutManager {
        /**
         * Disable predictive animations. There is a bug in RecyclerView which causes views that
         * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
         * adapter size has decreased since the ViewHolder was recycled.
         */
        @Override
        public boolean supportsPredictiveItemAnimations() {
            return false;
        }
    
        public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
        public NpaGridLayoutManager(Context context, int spanCount) {
            super(context, spanCount);
        }
    
        public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
            super(context, spanCount, orientation, reverseLayout);
        }
    }
    
  • 35

    在我的情况下(删除/插入我的数据结构中的数据)我需要清除回收池,然后通知数据集已更改!

    mRecyclerView.getRecycledViewPool().clear(); mAdapter.notifyDataSetChanged();

  • 0

    在我的情况下,我刚刚用 setHasStableIds(true); 删除了一行

  • 1

    在我修改 Adapter 实现以使用items数组的副本而不是引用之后,我的问题就消失了 . 每次我们在 RecyclerView 中显示新项目时,都会调用 setItems() 方法 .

    代替:

    private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
         private List<MyItem> mItems;  
    
        (....)
    
        void setItems(List<MyItem> items) {
            mItems = items;
        }
    }
    

    我做了:

    void setItems(List<MyItem> items) {
        mItems = new ArrayList<>(items);
    }
    
  • 11

    它也可以与同时多次设置适配器有关 . 我有一个回调方法,同时触发了5-6次,我在该回调中设置了适配器,因此RecycledViewPool无法同时处理所有这些数据 . 这是一个很大的机会,但无论如何你最好还是检查一下 .

  • 2

    我有同样的问题 . 当我快速滚动并调用API并更新数据时,就会发生这种情况 . 在尝试所有事情以防止崩溃之后,我找到了解决方案 .

    mRecyclerView.stopScroll();
    

    它会工作 .

  • 1

    我通过延迟 mRecycler.setAdapter(itemsAdapter) 解决了这个问题,直到用 mRecycler.addAll(items) 将所有项目添加到适配器后它才起作用 . 我不知道为什么我这样做,它来自一个图书馆's code that I looked over and saw those lines in the 420110 , I'我很确定这是它,如果有人可以确认它解释为什么会如此?不确定这是否是一个有效的答案

  • 0

    我曾经也得到了错误:

    Cause: 我试图从异步任务更新Recycler View,同时尝试获取旧的已删除的viewHolders;

    Code: 我按下按钮生成数据,逻辑如下

    • 清除回收站视图中的最后一项

    • 调用异步任务以生成数据

    • OnPostExecute更新Recycler视图和NotifyDataSetChanged

    Problem: 每当我在生成数据之前快速滚动我就会得到

    检测到不一致 . 无效的视图持有者适配器positionViewHolder java.lang.IndexOutOfBoundsException:检测到不一致 . 无效的项目位置20(偏移量:2).state:3

    Solution: 而不是在生成数据之前清除RecyclerView,而是将其保留,然后将其替换为新数据,即Call NotifyDatasetChanged,如下所示;

    @Override
            protected void onPostExecute(List<Objects> o) {
                super.onPostExecute(o);
                recyclerViewAdapter.setList(o);
                mProgressBar.setVisibility(View.GONE);
                mRecyclerView.setVisibility(View.VISIBLE);
            }
    
  • 0

    在我的情况下,我试图 change my adapter contents on a background thread but called notify on the main/ui thread.*

    That is not possible! 强制通知主线程的原因是recyclerview要求您在主线程上编辑备份适配器,即使在同一个调用堆栈上也是如此 .

    要解决此问题,请确保适配器的每个操作以及每个notify ...调用都在 ui/main thread 上进行!

相关问题