1、问题
在listView上下拉刷新或者滑动过程中经常碰到这个复现率比较高的崩溃问题
E/AndroidRuntime(16779): java.lang.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.
2、崩溃解释
这个报错是说adapter中的数据源更新了,但是listview没有及时收到通知进行更新。需确保adapter中数据源变化后调用notifyDataSetChanged()进行通知。
3、崩溃场景
1、请求数据是在子线程中进行,之后在该线程中对数据源dataList进行更新;
2、通过handler.sendmessage发消息到主线程notifydatachange()。【或者数据源更新和notifydatachange()都在主线程但不在同一个handler】
3、更新完adapter中的数据并通知listview后,修改了adapter中getCount()的返回值。
4、异常在代码中的体现
在ListView.java layoutChildren()方法中,如下。当mItenCount != mAdapter.getCount时就会异常。
protected void layoutChildren() {
......
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() + ")]");
}
......
}
5、分析原因
为什么mItemCount和adapter.getCount()会不相等?mItemCount什么时候会被赋值,layoutChilren()什么时候会被执行呢?
mItemCount赋值时机:
1、ListView.setAdapter()方法中有mItemCount = adapte.getCount()
2、ListView onMeasure()中也存在mItemCount = adapte.getCount(),即重绘会重新赋值,例如:notifydatachanged()之后会进行重绘。列表滑动结束action_up之后也会重绘。
layoutChidren()的调用时机:
1、ListView每次重绘onLayout()方法中会调用layoutChidren()。比如notyfydatachanged()之后都会重新requestlayout()。从而调用layoutChildren()
2、手势滑动 case MotionEvent.ACTION_MOVE: 中调用。
private void onTouchMove(MotionEvent ev, MotionEvent vtev) {
if (mHasPerformedLongPress) {
// Consume all move events following a successful long press.
return;
}
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
pointerIndex = 0;
mActivePointerId = ev.getPointerId(pointerIndex);
}
if (mDataChanged) {//数据源头更新 mDataChanged会为true
// Re-sync everything if data has been changed
// since the scroll operation can query the adapter.
layoutChildren();
}
......
那么问题就在于在子线程更新数据源之后(mDataChanged会置为true),由于notifydatachanged()在主线程,两个线程之间是存在时间差的,在这个时间差额中如果滑动列表就导致layoutChildren()被调用,而notifydatachanged()还未执行,所以mItemCount和adapter.getCount(数据源个数)不一致而崩溃。
如果在adapter中数据源更新后通过notifydatachanged()通知了listview,之后紧接着又修改了getCount()的值。notifydatachanged()之后会调用onMeasure()进行重绘,此时mItemCount = adapte.getCount()修改了mItemCount 的值为A,紧接着修改getCount()的值为B。此时有手势滑动case MotionEvent.ACTION_MOVE事件时,就会调用layoutChidren(),对mItemCount和adapte.getCount()的值进行判断。由于A!=B,因此会出现崩溃。
6、解决办法
把数据源更新和adapter.notifydatachanged放在同一个handler中。及时数据源更新和adapter.notifydatachanged都是在主线程也要保证不能是在两个handler中进行。尽量保证在notifydatachanged()之后不再对adapter进行任何操作,直到一次重绘结束。
参考:
listview源码分析
Android ListView工作原理完全解析,带你从源码的角度彻底理解
505

被折叠的 条评论
为什么被折叠?



