您的位置:首页 > 其它

RecyclerView:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter

2015-11-30 14:17 531 查看
使用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 theLayoutManager 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 havedone 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 RecyclerViewdoes 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. avoidingunnecessary 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-writesthem (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 everystep 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又添加了另一条数据。

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  RecyclerView bug