1.描述:RecyclerView BaseQuickAdapter notifyItemRemoved()在使用EmptyView,并且有headerView时,删除列表中唯一一个元素会崩溃
2.崩溃详情:
java.lang.IndexOutOfBoundsException
Inconsistency detected. Invalid view holder adapter positionViewHolder{9b31b49 position=1 id=-1, oldPos=2, pLpos:2 scrap [attachedScrap] tmpDetached no parent} android.support.v7.widget.RecyclerView{bf807a VFED.V... .F....ID 0,0-1080,1773 #7f0f0384 app:id/user_blogs_recycler_view}, adapter:com.ss.android.tuchong.mine.model.UserWorksListAdapter@7c23489, layout:android.support.v7.widget.GridLayoutManager@4a84c8e, context:com.ss.android.tuchong.main.controller.MainActivity@35eaf9b
1 android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5610) | |
2 android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5792) | |
3 android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5752) | |
4 android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5748) | |
5 android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2232) | |
6 android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:556) | |
7 android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1519) | |
8 android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:614) | |
9 android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170) | |
10 android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3763) | |
11 android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3527) | |
12 android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:4082) | |
13 android.view.View.layout(View.java:19612) | |
14 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
15 android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:606) | |
16 android.view.View.layout(View.java:19612) | |
17 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
18 android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791) | |
19 android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1780) | |
20 android.widget.LinearLayout.onLayout(LinearLayout.java:1546) | |
21 android.view.View.layout(View.java:19612) | |
22 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
23 android.support.v4.view.ViewPager.onLayout(ViewPager.java:1769) | |
24 android.view.View.layout(View.java:19612) | |
25 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
26 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323) | |
27 android.widget.FrameLayout.onLayout(FrameLayout.java:261) | |
28 android.view.View.layout(View.java:19612) | |
29 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
30 android.widget.RelativeLayout.onLayout(RelativeLayout.java:1080) | |
31 com.ss.android.tuchong.common.view.HeaderViewPager.onLayout(HeaderViewPager.java:315) | |
32 android.view.View.layout(View.java:19612) | |
33 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
34 android.widget.RelativeLayout.onLayout(RelativeLayout.java:1080) | |
35 android.view.View.layout(View.java:19612) | |
36 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
37 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323) | |
38 android.widget.FrameLayout.onLayout(FrameLayout.java:261) | |
39 android.view.View.layout(View.java:19612) | |
40 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
41 android.widget.RelativeLayout.onLayout(RelativeLayout.java:1080) | |
42 android.view.View.layout(View.java:19612) | |
43 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
44 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323) | |
45 android.widget.FrameLayout.onLayout(FrameLayout.java:261) | |
46 android.view.View.layout(View.java:19612) | |
47 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
48 android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791) | |
49 android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635) | |
50 android.widget.LinearLayout.onLayout(LinearLayout.java:1544) | |
51 android.view.View.layout(View.java:19612) | |
52 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
53 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323) | |
54 android.widget.FrameLayout.onLayout(FrameLayout.java:261) | |
55 android.view.View.layout(View.java:19612) | |
56 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
57 android.widget.LinearLayout.setChildFrame(LinearLayout.java:1791) | |
58 android.widget.LinearLayout.layoutVertical(LinearLayout.java:1635) | |
59 android.widget.LinearLayout.onLayout(LinearLayout.java:1544) | |
60 android.view.View.layout(View.java:19612) | |
61 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
62 android.widget.FrameLayout.layoutChildren(FrameLayout.java:323) | |
63 android.widget.FrameLayout.onLayout(FrameLayout.java:261) | |
64 com.android.internal.policy.DecorView.onLayout(DecorView.java:761) | |
65 android.view.View.layout(View.java:19612) | |
66 android.view.ViewGroup.layout(ViewGroup.java:6055) | |
67 android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2519) | |
68 android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2235) | |
69 android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1421) | |
70 android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6842) | |
71 android.view.Choreographer$CallbackRecord.run(Choreographer.java:1026) | |
72 android.view.Choreographer.doCallbacks(Choreographer.java:838) | |
73 android.view.Choreographer.doFrame(Choreographer.java:769) | |
74 android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1012) | |
75 android.os.Handler.handleCallback(Handler.java:789) | |
76 android.os.Handler.dispatchMessage(Handler.java:98) | |
77 android.os.Looper.loop(Looper.java:171) | |
78 android.app.ActivityThread.main(ActivityThread.java:6699) | |
79 java.lang.reflect.Method.invoke(Native Method) | |
80 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246) | |
81 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783) |
3.异常发生点:
/**
* Helper method for getViewForPosition.
* <p>
* Checks whether a given view holder can be used for the provided position.
*
* @param holder ViewHolder
* @return true if ViewHolder matches the provided position, false otherwise
*/
boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
// if it is a removed holder, nothing to verify since we cannot ask adapter anymore
// if it is not removed, verify the type and id.
if (holder.isRemoved()) {
if (DEBUG && !mState.isPreLayout()) {
throw new IllegalStateException("should not receive a removed view unless it"
+ " is pre layout" + exceptionLabel());
}
return mState.isPreLayout();
}
if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ "adapter position" + holder + exceptionLabel());
}
if (!mState.isPreLayout()) {
// don't check type if it is pre-layout.
final int type = mAdapter.getItemViewType(holder.mPosition);
if (type != holder.getItemViewType()) {
return false;
}
}
if (mAdapter.hasStableIds()) {
return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
}
return true;
}
其中holder是RecyclerView.BaseViewHolder类,它的toString()方法
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ViewHolder{"
+ Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId
+ ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
if (isScrap()) {
sb.append(" scrap ")
.append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]");
}
if (isInvalid()) sb.append(" invalid");
if (!isBound()) sb.append(" unbound");
if (needsUpdate()) sb.append(" update");
if (isRemoved()) sb.append(" removed");
if (shouldIgnore()) sb.append(" ignored");
if (isTmpDetached()) sb.append(" tmpDetached");
if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
if (isAdapterPositionUnknown()) sb.append(" undefined adapter position");
if (itemView.getParent() == null) sb.append(" no parent");
sb.append("}");
return sb.toString();
}
4.原因查找:
根据崩溃信息
Invalid view holder adapter positionViewHolder{9b31b49 position=1 id=-1, oldPos=2, pLpos:2 scrap [attachedScrap] tmpDetached no parent} android.support.v7.widget.RecyclerView{bf807a VFED.V... .F....ID 0,0-1080,1773 #7f0f0384 app:id/user_blogs_recycler_view}, adapter:com.ss.android.tuchong.mine.model.UserWorksListAdapter@7c23489, layout:android.support.v7.widget.GridLayoutManager@4a84c8e, context:com.ss.android.tuchong.main.controller.MainActivity@35eaf9b
和崩溃代码
if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ "adapter position" + holder + exceptionLabel());
}
可知,holder.mPosition为1,所以holder.mPosition >= mAdapter.getItemCount()为true,看下BaseQuickAdapter的getItemCount()方法
@Override
public int getItemCount() {
int count;
if (getEmptyViewCount() == 1) {
count = 1;
if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
count++;
}
if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
count++;
}
} else {
count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
}
return count;
}
其中,在使用EmptyView的情况下,getEmptyViewCount()方法返回1。
/**
* if show empty view will be return 1 or not will be return 0
*
* @return
*/
public int getEmptyViewCount() {
if (mEmptyLayout == null || mEmptyLayout.getChildCount() == 0) {
return 0;
}
if (!mIsUseEmpty) {
return 0;
}
if (mData.size() != 0) {
return 0;
}
return 1;
}
那么,在添加了headerView又通过data.remove(0)将最后一个元素删除时,count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
如果没有footer、loadMore的情况下,count为1。这样就符合崩溃条件,导致崩溃了。
5.解决方案:
一开始想用如下方案,没有问题了
if (mAdapter.data.size == 0) {
mAdapter.notifyDataSetChanged()
} else {
mAdapter.notifyItemRemoved(mAdapter.headerLayoutCount + index)
}
但看了下BaseQuickAdapter的源码,发现有remove(@IntRange(from = 0) int position)方法
/**
* remove the item associated with the specified position of adapter
*
* @param position
*/
public void remove(@IntRange(from = 0) int position) {
mData.remove(position);
int internalPosition = position + getHeaderLayoutCount();
notifyItemRemoved(internalPosition);
compatibilityDataSizeChanged(0);
notifyItemRangeChanged(internalPosition, mData.size() - internalPosition);
}
试了下,也没有问题,第一次发现adapter的notify()可以这样用,BaseQuickAdapter也是做到极致了。