ViewPager異常,對ViewPager源碼分析


今天遇到以下異常:

java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's 
contents without calling PagerAdapter#notifyDataSetChanged!

在描述問題解決之前,先說下項目列表顯示的機制吧
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()方法就解決問題了!

記錄下來,加深印象!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM