双思路解决The content of the adapter has changed but ListView did not receive a notification崩溃问题
双思路解决The content of the adapter has changed but ListView did not receive a notification崩溃问题
为什么说双思路呢,因为自己在网上查到了这个异常出现的情况,但是搜索出来的结果都是千篇一律的,然而并没有实质地解决自己的问题,然后通过仔细思考后发现,除了网上的思路1,还有一个思路2,修改后确实不崩溃了,然后才在思路2的基础上真正解决的思路1的问题。在自己对策了该问题后发现,其实还是本身基本功不够扎实啊!!!!!!希望能帮助到遇到问题的同学。
思路一:
思路1就是网上所说的,这里就简单一句话带过就O得K了;
一定要确保adapter中的数据改变后马上调用notifyDataSetChanged()方法通知更新数据,并且是主UI线程中进行刷新处理。
顺便说一下,比较下当前Looper和主Looper就能够得出当前是否在主线程了。
public static boolean isMainLoop() {
boolean ret;
if (Looper.getMainLooper() == Looper.myLooper()) {
Log.i(TAG, "I am MainLooper");
ret = true;
} else {
Log.i(TAG, "I am NoLooper");
ret = false;
}
return ret;
}
思路二:
直接上代码再讲解。
原本写法:
private List<MyBean> mListInfo;
public void updateList(List<MyBean> listInfo){
mListInfo = listInfo;
notifyDataSetChanged();
}
修改后写法:
private List<MyBean> mListInfo = new ArrayList<>();
public void updateList(List<MyBean> listInfo){
mListInfo.clear();
mListInfo.addAll(listInfo);
notifyDataSetChanged();
}
看完后,有啥感觉不?
我们在列表setAdapter时,一般会调用一下updateList把外部new出来的listInfo赋值给内部的mListInfo,只要赋值过一次,就会出现下面的情况了:
原本写法中,listInfo直接赋值给mListInfo,Java不知道怎么解释,可能就是mListInfo持有了listInfo的引用吧,在C/C++中,就可以说是listInfo和mListInfo都时同一块内存地址里面的值,就像指针指向了同一块内存地址,listInfo改变时,Adapter里面的值也被改变了。
listInfo在Adapter外部也是一个new出来的列表,这个值在外部可能会随时被赋值,用于记忆新的数据,但是可能刚被赋值时,我们可能还会进行一些操作后,再去通知更新列表,这就导致了listInfo在被赋值后,Adapter里面的mListInfo同样也被改变掉了,但是我们并没有马上进行notifyDataSetChanged(),所以才导致IllegalStateException的异常。
修改后的写法,就是Adapter内部同样也去new一个新的列表,申请一块内存,等到列表要更新时,我们先清空内部mListInfo,然后将传进来的listInfo中的数据,拷贝到mListInfo,然后马上通知列表更新,这样就保证了内部的mListInfo并不会无缘无故地被改变值,想要改变,只能通过updateList()方法进行更新。
思考
虽然说,思路二的方法能够解决异常崩溃的问题,但是本质上还是listInfo在外部被突然改变了,通过这样的思考,我们就可以顺藤摸JJ地去排查我们外部修改了listInfo的地方,是否应该修改还是需要及时通知数据变化了。
最后
最后就把Android源码贴一下抛出异常的地方吧!!!
抛出异常的地方:
// Handle the empty set by removing all views that are visible
// and calling it a day
if (mItemCount == 0) {
resetList();
invokeOnItemScrollListener();
return;
} else if (mItemCount != mAdapter.getCount()) {
throw new IllegalStateException("The content of the adapter has changed but "
+ "ListView did not receive a notification. Make sure the content of "
+ "your adapter is not modified from a background thread, but only from "
+ "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
+ "when its content changes. [in ListView(" + getId() + ", " + getClass()
+ ") with Adapter(" + mAdapter.getClass() + ")]");
}
listView进行SetAdapter时会对mItemCount赋值:
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);