欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

双思路解决The content of the adapter has changed but ListView did not receive a notification崩溃问题

程序员文章站 2022-07-15 12:07:41
...

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

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

修改后写法:

private List<MyBean> mListInfo = new ArrayList<>();

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