ViewPager非同步新增資料異常 The application's PagerAdapter changed the adapter's contents without calling Pag
專案中用到ViewPager左右滑動圖片,圖片從網上獲取,非同步新增到ViewPager的Adapter中。出現強關,log如下
06-25 15:01:44.396 E/AndroidRuntime(21181): java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 6, found: 7 Pager id: com.fineos.music:id/focus_pager
Pager class: class android.support.v4.view.ViewPager Problematic adapter: class com.fineos.music.HQadapter.OnlineFocusFragmentPagerAdapter
06-25 15:01:44.396 E/AndroidRuntime(21181): at android.support.v4.view.ViewPager.populate(ViewPager.java:962)
06-25 15:01:44.396 E/AndroidRuntime(21181): at android.support.v4.view.ViewPager.populate(ViewPager.java:914)
06-25 15:01:44.396 E/AndroidRuntime(21181): at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436)
06-25 15:01:44.396 E/AndroidRuntime(21181): at android.view.View.measure(View.java:15623)
在描述問題解決之前,先說下專案列表顯示的機制吧
1、資料:
1)、Adapter接受到的是List,List容器中存放的是資料的實體類
2)、所有View存放在Map中,getCount()方法返回的是Map的size
2、檢視:
1)、Adapter首先會根據List的大小和展現的View,預載入,這裡是每次下載48條資料,每頁12條,共4頁
2)、當ViewPager的滾動狀態為IDLE的情況下,會以當前頁為基準,向前建立一頁View,向後建立兩頁View
3)、所有View儲存在Map中,當在呼叫instantiateItem方法的時候,直接從Map裡邊取
3、更新:
1)、當資料下載完成,在主執行緒更改介面卡中的List容器,並且呼叫notifyDataSetChanged();
2)、onPageSelected觸發會再次預載入的下一頁資料,更新完畢還會執行上一步
好,進入正文
很多帖子提到ADT更新到22之後,檢查更加嚴格,因此,每次資料更改都要呼叫notifyDataSetChanged方法,
我確實是這麼做了,非同步下載資料,下載完資料傳送到主執行緒進行notifyDataSetChanged,結果,還是拋異常。
之前沒看過ViewPager原始碼,這次就大概跟蹤下方法吧!
通過搜尋ViewPager類,找到異常丟擲位置,在populate方法中
1 final int N = mAdapter.getCount(); 2 // code here ... 3 if (N != mExpectedAdapterCount) { 4 String resName;5 try { 6 resName = getResources().getResourceName(getId()); 7 } catch (Resources.NotFoundException e) { 8 resName = Integer.toHexString(getId()); 9 } 10 throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + 11 " contents without calling PagerAdapter#notifyDataSetChanged!" + 12 " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + 13 " Pager id: " + resName + 14 " Pager class: " + getClass() + 15 " Problematic adapter: " + mAdapter.getClass()); 16 }
關鍵就是mExpectedAdapterCount,那繼續找mExpectedAdapterCount的宣告和使用。
首先在setAdapter(PagerAdapter adapter)方法中找到賦值的地方,但是,不是設定介面卡這個地方造成的異常,
所以,繼續查詢。
最後查詢到的只有在dataSetChanged()中再次使用過,程式碼如下:
1 void dataSetChanged() { 2 // This method only gets called if our observer is attached, so mAdapter is non-null. 3 4 final int adapterCount = mAdapter.getCount(); 5 mExpectedAdapterCount = adapterCount; 6 // code here... 7 }
在PagerAdapter中呼叫notifyDataSetChanged()方法,資料更新的時候,mExpectedAdapterCount會被重新賦值
mExpectedAdapterCount和N不同,那隻能查下dataSetChanged()和populate()呼叫的先後順序了
dataSetChanged()肯定是notifyDataSetChange()方法觸發,那就查詢populate()
不說分析的過程了,直接上結果!如下
ViewPager每次翻頁方法執行順序:
dispatchKeyEvent->executeKeyEvent->arrowScroll->
pageLeft/pageRight->setCurrentItem->setCurrentItemInternal
在setCurrentItemInternal方法中,各種方法呼叫,會執行多次populate()方法,因此,會呼叫到多次getCount()
來獲取N的值,如下圖
問題出來了,當翻頁的時候,populate()方法會呼叫多次,直到狀態為IDLE的時候,會建立預載入的一頁檢視,
此時,Adapter中存放View的Map會增加,getCount返回值變大。
這時候資料並未下載下來,那並不會notifyDataSetChanged()方法,mExpectedAdapterCount的值還是上次的值
因此,如下條件成立,進入程式碼,丟擲異常
1 if (N != mExpectedAdapterCount) { 2 // code here... 3 }
如下圖(最後一條Log為5,其實之後還會列印多次只是這時已經在populate()方法除拋異常,不會再繼續執行):
==================================================================
仔細思考思考,其實是對notifyDataSetChanged()方法的呼叫時機有誤解,並不是介面卡資料更新的時候呼叫,
而是在getCount()發生改變的時候去呼叫,哪裡影響了getCount(),就應該再哪裡呼叫!
因此,在列表機制的第2-2步中去呼叫notifyDataSetChanged()方法。
也就是在ViewPager的滑動監聽中的
public void onPageScrollStateChanged(int i) {
方法中使用,判斷state狀態呼叫notify。
但是實際使用中,自動滑動沒有問題,手動滑動依然會出現問題。
viewpager的adapter中的getcount方法如下
@Override
public int getCount() {
Log.d(TAG, "mOnlineFocusFragmentList mCount : " +
(mOnlineFocusFragmentList == null ? 0 : mCount + 1) );
return mOnlineFocusFragmentList == null ? 0 : mCount + 1;
}
錯誤log依然同上。
從log往上看到,兩個count的值一直都是相等的,當viewpager中的fragment的oncreateView 方法走到時,mCount的值發生變化,並且出現Exception。
所以在fragment的oncreateView和ondestroy方法加上 adapter的notify方法,問題解決!!