Override ListView getAdapter造成的後果
最近工作中,發現了一個bug,是和ListView Adapter有關的。產生了FC,描述資訊大約是
"The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(xxx) with Adapter(HeaderViewListAdapter)]"
它的大意是,Adapter內的資料發生了變化,但是UI卻沒有更新,您是否忘記調用了notifyDataSetChanged?
這實際上是一個非常有誤導的資訊。一般情況下,我們不會忘記調用該函數的。但是如果我們不小心,從listview繼承一個新的類,並override它的getAdapter方法,就可能會出問題了。
ListView是支援HeaderView和footerView的,即在listview的最初和最末尾的位置添加一些特殊的view。它的實現方法,就是通過一個HeaderViewListAdapter。
HeaderViewListAdapter會封裝一個Adapter,這個是由使用者自己設定的。ListView中對應的代碼是
@Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } resetList(); mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; }
ListView的getAdapter返回的是mAdapter,即可能是一個HeaderViewListAdapter.
如果override getAdapter,並返回HeaderViewListAdapter內部封裝的Adapter,就會出問題。也就是上面提到的FC.
這種問題是怎麼出現呢?
首先,這個異常拋出的位置,是在函數layoutChildren中,拋出的條件是mItemCount != mAdapter.getCount(),代碼如下:
else if (mItemCount != mAdapter.getCount()) { throw new IllegalStateException("The content of the adapter has changed but " + "ListView did not receive a notification. Make sure the content of " + "your adapter is not modified from a background thread, but only from " + "the UI thread. Make sure your adapter calls notifyDataSetChanged() " + "when its content changes. [in ListView(" + getId() + ", " + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]"); }
那麼mItemCount的值是在哪裡賦值呢?mItemCount不是ListView的成員,而是ListView的超超類:AdapterView的成員,這個值也是在DataObserver.onChanged中設定的,您可參考AdapterView的源碼:
class AdapterDataSetObserver extends DataSetObserver { private Parcelable mInstanceState = null; @Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); //這裡!注意用法getAdapter() // Detect the case where a cursor that was previously invalidated has // been repopulated with new data. if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { rememberSyncState(); } checkFocus(); requestLayout(); }
如果 getAdapter() != mAdapter就會發生問題:getAdatper返回的是mAdapter(即HeaderListViewAdapter),那麼,mAdapter.getCount() == getAdapter().getCount() + header view count + footer view count.
出現上面的問題就在所難免了。