The application's PagerAdapter changed the adapter's
contents without calling PagerAdapter#`notifyDataSetChanged`!
1. 异常发生的地方
ViewPager.java
void populate(int newCurrentItem) {
...
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
if (N != mExpectedAdapterCount) {
String resName;
try {
resName = getResources().getResourceName(getId());
} catch (Resources.NotFoundException e) {
resName = Integer.toHexString(getId());
}
throw new IllegalStateException("The application's PagerAdapter changed the adapter's"
+ " contents without calling PagerAdapter#notifyDataSetChanged!"
+ " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N
+ " Pager id: " + resName
+ " Pager class: " + getClass()
+ " Problematic adapter: " + mAdapter.getClass());
}
...
}
2.分析异常原因
N 和 mExpectedAdapterCount 不相等的时候就会产生这个异常。
N为mAdapter.getCount() 最新的Adapter的数量,
mExpectedAdapterCount是之前保存的Adapter数量,应该是某个地方我的Adapter更新了,但是mExpectedAdapterCount没有更新造成的问题。
3. 自己的代码分析
//清空Fragment的缓冲,重新创建Fragment 列表填充到ViewPager里面
fun setNewData(list:List<MenuData>,viewPager: ViewPager,manager:FragmentManager){
mData.clear()
mData.addAll(list)
//清空FragmentManager里面的所有的缓存
var aClass = manager.javaClass
try {
//1.获取其mAdded字段
var f: Field = aClass.getDeclaredField("mAdded")
f.setAccessible(true)
//强转成ArrayList
//清空缓存
(f.get(manager)as ArrayList<Fragment>).clear()
//2.获取mActive字段
f = aClass.getDeclaredField("mActive")
f.setAccessible(true)
//强转成SparseArray
//清空缓存
(f.get(manager) as HashMap<String, Fragment>).clear()
} catch (e: Exception) {
e.printStackTrace()
}
mViewPager = viewPager
//希望ViewPager 初始化的时候 所有的Fragment都可以创建
mViewPager?.offscreenPageLimit = list.size
//设置新的Adapter
mViewPager?.adapter = InnerPagerAdapter(manager,mData)
//清空父布局的所有缓存的View
mTabsContainer?.removeAllViews()
Log.e("gacmy","notifyDataChange ${list.size}")
notifyDataChange()
prePos = 0
curPos = 0
Log.e("gacmy","setNewData ${list.size}")
mViewPager?.adapter?.notifyDataSetChanged()
setSelectTab(0,0)
}
ViewPager设置Fragment列表第二次重新更新ViewPager列表的时候就会报错
感觉很奇怪,分析哪些地方会调用populate()这个方法
ViewPager.java 里面dataSetChange setOffscreenPageLimit 这些地方会调用
offscreenPageLimit 方法会调用 日志这个方法之后就不会调用了,应该是这里报错了
4. 原因分析
setOffscreenPageLimit 调用的时候mAdapter里的数据个数发生了变化,导致调用这个方法的时候和上一次
ViewPager缓存的Adapter的个数不一致。
上面代码设置adapter的代码 在setOffscreenPageLimit的后面,说明数据在
setOffscreenPageLimit之前发生了变化,但是mExpectedCount却没来得及更新
mData.clear()
mData.addAll(list)
只有这里数据发生了变化,说明我的Adapter持有的引用是外部的mData,我把mData的数据增加减少了,但是却没有通知ViewPager,导致调用setOffscreenPageLimit 检测的是老数据.
下面看看Adapter的代码看看
class InnerPagerAdapter:FragmentPagerAdapter{
private var mFragments :
java.util.ArrayList<MenuData>? = null
constructor(
fm: FragmentManager,
fragments: java.util.ArrayList<MenuData>
):super(fm) {
//这里持有外部引用
mFragments = frgments
}
override fun getItem(position: Int): Fragment {
return mFragments!![position].fragment!!
}
override fun getCount(): Int {
return mFragments!!.size
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
}
override fun getItemPosition(`object`: Any): Int {
return FragmentPagerAdapter.POSITION_NONE
}
}
adapter引用了外部的mData 导致mData数据跟新,ViewPager缓存的数据没有及时更新
所以很多代码为了线程安全,数据安全都会在内部持有自己的引用,不能引用外部引用导致代码的不安全性。
下面修该后的代码,adapter里的引用自己持有,不要和外部产生关联。
mFragments = ArrayList<MenuData>()
mFragments?.addAll(fragments)