Listview更新数据时崩溃The content of the adapter has changed but did not receive a notification.

说明:在工作中遇到的问题记录下来,欢迎批评和指正~


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.csdn.net/guolin_blog/article/details/44996879
http://www.cnblogs.com/monodin/p/3874147.html








你可能感兴趣的:(Android)