前些日子在github上遇到了一個(gè)有趣的問(wèn)題,是在一個(gè)叫“xRecyclerView”的項(xiàng)目里的issue薇宠,一哥們說(shuō)他給列表加兩個(gè)head就會(huì)報(bào)錯(cuò)椒涯,而我則不會(huì)废岂。隨后又有兩個(gè)哥們表示他們也有同樣的問(wèn)題:
java.lang.IllegalArgumentException: called detach on an already detached child ViewHolder{11ae0c3d position=2 id=-1, oldPos=-1, pLpos:-1 scrap [attachedScrap] tmpDetached no parent}...
于是我貼上了我的代碼:
View view1 = LayoutInflater.from(activity).inflate(layoutId1, null);
View view2 = LayoutInflater.from(activity).inflate(layoutId2, null);
xRecyclerView.addHeaderView(view1);
xRecyclerView.addHeaderView(view2);
而其中兩個(gè)哥們的錯(cuò)誤代碼如下:
headerRecommend=LayoutInflater.from(getActivity()).inflate(R.layout.fragment_find_item_recommend, (ViewGroup) mView.findViewById(android.R.id.content), false);
headerRank=LayoutInflater.from(getActivity()).inflate(R.layout.fragment_find_item_rank, (ViewGroup) mView.findViewById(android.R.id.content), false);
rvTipsRecommendList.addHeaderView(headerRecommend);
rvTipsRecommendList.addHeaderView(headerRank);
headerview = new CustomizeHeaderLayout(this);
headerview1 = new CustomizeHeader1Layout(this);
headerview2 = new CustomizeHeader2Layout(this);
listview.addHeaderView(headerview);
listview.addHeaderView(headerview1);
listview.addHeaderView(headerview2);
public CustomizeHeaderLayout(Context context) {
super(context);
init();
}
private void init() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.layout_customize_headview, null);
...
}
先看源碼袒啼,recyclerView和XRecyclerView的源碼。在recyclerView的源碼里拋出這個(gè)異常的地方是這樣判斷的:
public void detachViewFromParent(int offset) {
if (vh != null) {
if (vh.isTmpDetached() && !vh.shouldIgnore()) {
throw new IllegalArgumentException("called detach on an already"
+ " detached child " + vh);
}...
vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
}
}
這個(gè)vh.isTmpDetached()和!vh.shouldIgnore()是一些c里面的二進(jìn)制運(yùn)算包各,先放在一邊。我們回過(guò)頭看log护姆,可以看到vh對(duì)象打印出的東西有點(diǎn)不一樣卵皂,作者應(yīng)該是重寫了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(); }
??我的正常的log打出的是
ViewHolder{335f5b75 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} ViewHolder{1e435ef3 position=1 id=-1, oldPos=-1, pLpos:-1 no parent} ViewHolder{f598161 position=2 id=-1, oldPos=-1, pLpos:-1 no parent}
??對(duì)比可以得出結(jié)論是,我的在isScrap()和isTmpDetached()這兩個(gè)判斷是始終沒(méi)有走的耙替,而他們兩個(gè)都走到這個(gè)判斷里面去了林艘。那就是這個(gè)isScrap()和isTmpDetached()里有問(wèn)題!再結(jié)合上文判斷異常部分的代碼钢坦,isTmpDetached()這個(gè)方法顯得詭異了許多。
??通過(guò)我的有道神器禾酱,知道了Detached是‘分離’的意思,那detachViewFromParent就可以理解為‘從父view中分離子view’吧滓走,如果不報(bào)錯(cuò)搅方,還會(huì)addFlags(ViewHolder.FLAG_TMP_DETACHED),雖然我看不懂大神的二進(jìn)制算法吧慢,但是現(xiàn)在我好像可以從字里行間里隱約感覺(jué)到是怎么回事了怖喻。那就是在調(diào)用了detachViewFromParent之后,使isTmpDetached()成立涕癣,又一次調(diào)用了這個(gè)方法距潘,就報(bào)了這個(gè)錯(cuò)誤俭尖!想明白的瞬間背上似乎有了層涼氣稽犁,在凌晨2點(diǎn)的夜里還是蠻嚇人的...但我還是不明白detachViewFromParent的調(diào)用機(jī)制已亥,歸根結(jié)底我還是沒(méi)有明白這個(gè)bug出現(xiàn)的原因,只能明天再做調(diào)查了捆姜。
ps:有個(gè)挪威小哥也有同樣的問(wèn)題:
https://github.com/martijnvdwoude/recycler-view-merge-adapter/issues/4
但我沒(méi)有看太懂
——2017-3-1 2:32