说明:在工作中遇到的问题记录下来,欢迎批评和指正~
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. [in ListView(2131034604, class android.widget.ListView) with Adapter(class com.nodin.sarah.HeartListAdapter)]
2、崩溃解释
是说数据源更新了,但是listview没有及时收到通知进行更新。确保adapter内容更新是在主线程。
3、崩溃场景
1、请求数据是在子线程中进行,之后在该线程中对数据源dataList进行更新;
2、通过handler.sendmessage发消息到主线程notifydatachange。【或者数据源更新和notifydatachange都在主线程但不在同一个handler】
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(数据源个数)不一致而崩溃。
6、解决办法
把数据源更新和adapter.notifydatachanged放在同一个handler中。及时数据源更新和adapter.notifydatachanged都是在主线程也要保证不能是在两个handler中进行。
参考:
listview源码分析
http://blog.youkuaiyun.com/guolin_blog/article/details/44996879
http://www.cnblogs.com/monodin/p/3874147.html