双思路解决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 mListInfo;

public void updateList(List listInfo){
    mListInfo = listInfo;
    notifyDataSetChanged();
}

修改后写法:

private List mListInfo = new ArrayList<>();

public void updateList(List 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);

你可能感兴趣的:(Android)