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.
*
* 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也是做到极致了。