关于android:RecyclerView:检测到不一致。 商品位置无效

RecyclerView: Inconsistency detected. Invalid item position

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

java.lang.IndexOutOfBoundsException:
Inconsistency detected. Invalid item position 2(offset:2).state:3

对我来说,它看起来像RecyclerView内部的一个内部错误,因为我无法想到这是由我们的代码直接导致的任何方式......

有谁遇到过这个问题?

什么是解决方案?

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

但是,如果可能的话,我想更好地理解这个问题(并且可能在其源头修复它),而不是掩盖它。

这个bug很难重现,但是当它发生时它是致命的。

完整的堆栈跟踪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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


我有一个(可能)相关的问题 - 使用RecyclerView输入一个活动的新实例,但是使用较小的适配器为我引发了这个崩溃。

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

幸运的是,只有在启用PredictiveAnimations的情况下才会执行此操作,因此我的解决方案是子类GridLayoutManager(LinearLayoutManager具有相同的问题和'fix'),并覆盖supportsPredictiveItemAnimations()以返回false:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * 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);
    }
}


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

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


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


我有类似的问题,但不完全一样。在我的情况下,1点我正在清理传递给recyclerview的数组

1
mObjects.clear();

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


我通过延迟mRecycler.setAdapter(itemsAdapter)直到用mRecycler.addAll(items)将所有项添加到适配器后解决了这个问题并且它工作了。我不明白为什么我这样做,它来自我看过的图书馆代码并看到"错误的顺序"中的那些行,我很确定这是它,如果有人可以确认它解释为什么它是所以?不确定这是否是一个有效的答案


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

1
mRecyclerView.stopScroll();

它会工作。


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

固定:

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

1
2
3
4
5
6
7
mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();

===> Error occur

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

1
2
3
4
5
6
List res = new ArrayList();
…..
res.add();  //add item or modify list
….
mainList = res;
notifyDataSetChanged();

感谢Nhan Cao给予了这么大的帮助:)


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

1
2
3
4
5
mList.clear();
mAdapter.notifyDataSetChanged();

mList.addAll(newData);
mAdapter.notifyDataSetChanged();


我正在改变背景ThreadRecyclerView的数据。我得到了与OP相同的Exception。我在更改数据后添加了这个:

1
2
3
4
5
6
myRecyclerView.post(new Runnable() {
    @Override
    public void run() {
        myRecyclerAdapter.notifyDataSetChanged();
    }
});

希望能帮助到你


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

代替:

1
2
3
4
5
6
7
8
9
private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
     private List<MyItem> mItems;  

    (....)

    void setItems(List<MyItem> items) {
        mItems = items;
    }
}

我做了:

1
2
3
void setItems(List<MyItem> items) {
    mItems = new ArrayList<>(items);
}


使用

1
notifyDataSetChanged()

代替

1
notifyItemRangeInserted(0, YourArrayList.size())

在这种情况下。


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

例如

1
2
3
//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear(); //更新回收视图的列表

1
adapter.notifyDataSetChanged();


您只需要在OnPostExecute()上清除列表,而不是在执行Pull to Refresh时清除

1
2
3
4
5
6
7
8
9
10
// 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

            }
        });

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

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

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


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

1
2
3
4
5
6
7
activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        changeData();
        notifyDataSetChanged();
    }
});

然后它停止了崩溃。


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

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

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

mList = new ArrayList<>(passedList);

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


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


在我的情况下,我试图在后台线程上更改我的适配器内容,但在main / ui线程上调用notify *。

这是不可能的!强制通知主线程的原因是recyclerview希望您在主线程上编辑后备适配器,即使在同一个调用堆栈上也是如此。

要解决此问题,请确保在ui /主线程上对适配器以及每个notify ...调用进行每次操作!


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


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

1
myLayoutmanager.removeAllViews();


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

mRecyclerView.getRecycledViewPool().clear();


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


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

你的代码可能是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 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() ;
 }

}

响应很晚,但这可能有助于某人的功能。

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


对我来说,添加这行代码后它起作用了:

1
mRecyclerView.setItemAnimator(null);


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

1
2
3
4
5
6
7
public void add(Data item) {
        if(!params.contains(item){
           params.add(item);
           notifyItemInserted(getItemCount() - 1);
        }
    }
}

这只是解决方案,即使从上述解决方案中尝试了很多,也对我有用。

1.)使用

1
2
3
CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);

2.)在适配器中写入此方法

1
2
3
4
5
public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}

stockListModels - >此列表是您在适配器中使用的。


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

1
2
3
4
5
6
7
8
 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 );
        }

我找到
设置
mRecycler.setLayoutFrozen(真);
在swipeContainer的onRefresh方法中。

为我解决了这个问题。

1
2
3
4
5
6
7
8
swipeContainer.setOnRefreshListener(new   SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            orderlistRecycler.setLayoutFrozen(true);
            loadData(false);

        }
    });

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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
      }
    });

}

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


我曾经也得到了错误:

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

代码:按下按钮生成数据,逻辑如下

  • 清除回收站视图中的最后一项
  • 调用异步任务以生成数据
  • OnPostExecute更新Recycler视图和NotifyDataSetChanged
  • 问题:每当我在生成数据之前快速滚动我就会得到

    Inconsistency detected. Invalid view holder adapter positionViewHolder
    java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 20(offset:2).state:3

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

    1
    2
    3
    4
    5
    6
    7
           @Override
            protected void onPostExecute(List<Objects> o) {
                super.onPostExecute(o);
                recyclerViewAdapter.setList(o);
                mProgressBar.setVisibility(View.GONE);
                mRecyclerView.setVisibility(View.VISIBLE);
            }


    add_location.removeAllViews();

    1
    2
    3
    4
    5
                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();


    当我没有识别时,我遇到了问题我同时使用不同的线程调用了两次。

    notifyDataSetChanged

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


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

    1
    2
    3
    4
    5
    6
    pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            doStuff(position);
                        }
                    });

    必须替换为:

    1
    2
    3
    4
    5
    6
    pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            doStuff(pholder.getAdapterPosition());
                        }
                    });

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


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
     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());

     }