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.

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工作原理完全解析,带你从源码的角度彻底理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值