1. 程式人生 > >ViewPager非同步新增資料異常 The application's PagerAdapter changed the adapter's contents without calling Pag

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方法,問題解決!!