RecyclerView:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter

当在 RecyclerView 中动态添加或删除数据时,可能会遇到IndexOutOfBoundsException。这是由于开发者错误导致的,需要修复。RecyclerView 的布局管理器期望从状态获取项目数量,而 Notify 事件应与数据更改同步调用,以确保一致性。避免在 RecyclerView 分离时触发事件,且不应在未插入项时调用 notifyInserted()。正确做法是在同一调用堆栈中更新数据并通知 RecyclerView。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用RecyclerView时,在动态添加/删除数据时,很有可能会出现下面的错误:

java.lang.IndexOutOfBoundsException: 
Inconsistency detected. Invalid view holder adapter positionViewHolder{424b7690 position=7 id=-1, oldPos=8,pLpos:8 scrap tmpDetached no parent} at 
android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4349)

官方提出过如下内容:(注意红色字体部分)

1.ListView and RecyclerView are different

ListView and RecyclerView are different. RecyclerView is designed to work with different components and makes certain promises. For the error above, exception happens when LayoutManager tries to get a View for a position. A count is already provided to the LayoutManager at the beginning of the layout, this is a promise and guaranteed not to change until the layout is complete. RecyclerView cannot say “you have 6 items” and when LayoutManager asks for item at position 5, return null. LayoutManager might have done its own calculations based on that count, doing so may leave its state unstable. On the other hand, ListView has full control, thus it can forgive these things. Besides that, ListView does not do anything clever about the adapter contents whereas RecyclerView does a lot to support animations. The two are fairly different. To be honest, if you do not dispatch detailed notify events, there is little to no benefit on moving to RecyclerView. If you dispatch them, it will both help UX and performance. (e.g. avoiding unnecessary rebinds)

RecyclerView throws an exception because problem happens due to a developer error and should be fixed. If it is a RecyclerView, we have to fix it. In both cases, for a consistent and stable API, forgiving developer errors (both ends) is not a sustainable solution.

About adapter count, that getItemCount is one API I regret leaving public (was an old API, had to be kept for some backward compatibility). 
LayoutManagers are expected to get item count from the State. If you check framework layout managers, all work w/ state. There is a strict abstraction between the Adapter and LayoutManager (due to animations). Even for notify events, RecyclerVIew re-writes them (in a consistent way) to suit them for two pass animations. There is a lot going on there, hard to explain here. (see docs: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State.html#getItemCount()) 
RecyclerView also provides an API to convert layout positions to adapter positions if necessary. 
convertPreLayoutPositionToPostLayout : https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Recycler.html#convertPreLayoutPositionToPostLayout(int)

The trigger for the bug might be events while RV is detached, some Runnable may not be running due to View being detached. Some info / logs would be very helpful so that i can create a test case and fix it. 
Thanks.


2.let RV know about it

Great to hear that issue is fixed. I did not understand why you are calling notifyInserted w/o inserting them. It will definitely create a problem. This explains why RV expects to have more items in the adapter. 
Only call these events right after you change the data. (has to be in the same call stack ~ main looper loop~)

Notify events are handled asynchronously. So you can call as many notify events as you want and RV will handle all of them in the next layout pass.It batches them etc. You just need to guarantee that all of them are consistent with each other. That is, in every step you change adapter, you should let RV know about it. Your events should be consistent. For example, if you want to remove first two elements 1 by one, you should call:

mData.removeItemAt(0); 
notifyItemRemoved(0); 

mData.removeItemAt(0); 
notifyItemRemoved(0);

A common mistake would be to think that you need to call notifyItemRemoved(0); then notifyItemRemoved(1);. This is NOT true as RV knows items will shift if first item is removed. This is also consistent w/ what you would do while handling a list.

This is the simplest way to get them right. Technically, you can let RV know right after you update the backing data, as long as you do it in the same call stack. 
e.g. 
mData.removeItemAt(0); 
mData.removeItemAt(0); 
notifyItemRangeRemoved(0, 2); //2 items, starting from 0.

So your code probably works fine if notifyDataSetChanged arrives before the next layout calculation but fails otherwise.

When you call notifyDataSetChanged, you void all previous notify events (in that frame). Don’t call notifyDataSetChanged if you don’t have to.

Good luck and thanks for the update, I’m closing the issue.

不想翻译,或看不懂也没关系,注意红色字体部分就行了。

其实就是说,list数据的添加/删除 要与adapter的添加/删除 要是同步的。

不要 list先添加一条数据,然后还没等adapter也添加时,list又添加了另一条数据。




 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值