• 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()方法就解决问题了!

    记录下来,加深印象!

  • 相关阅读:
    CF809E Surprise me!
    2019-4-12-WPF-绑定的默认模式
    2019-4-12-WPF-绑定的默认模式
    2019-2-28-C#-16-进制字符串转-int-
    2019-2-28-C#-16-进制字符串转-int-
    2019-10-23-WPF-使用-SharpDx-异步渲染
    2019-10-23-WPF-使用-SharpDx-异步渲染
    2019-8-31-ASP.NET-Core-开启后台任务
    2019-8-31-ASP.NET-Core-开启后台任务
    2019-8-24-win10-uwp-读取文本GBK错误
  • 原文地址:https://www.cnblogs.com/dzboy/p/3629049.html
Copyright © 2020-2023  润新知