Android,ListView IllegalStateException:“适配器的内容已更改但ListView未收到通知”

发布于 2024-09-07 19:48:07 字数 1806 浏览 6 评论 0原文

我想要做什么:运行一个后台线程来计算 ListView 内容并部分更新 ListView,同时计算结果。

我知道我必须避免:我不能弄乱后台线程中的 ListAdapter 内容,因此我继承了 AsyncTask 并从 onProgressUpdate 发布结果(将条目添加到适配器)。我的适配器使用结果对象的ArrayList,这些数组列表上的所有操作都是同步的。

其他人的研究:有非常有价值的数据在这里。我还遭受了约 500 个用户组几乎每天都会发生的崩溃,当我在 onProgressUpdate 中添加 list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) 块时,崩溃降低了 10 倍,但没有消失。 (答案中建议)

我有时会得到什么:请注意,这种情况很少发生(对于 3500 个用户之一来说,每周一次)。但我想完全摆脱这个错误。这是部分堆栈跟踪:

`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. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1432)
at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)
at android.widget.ListView.onTouchEvent(ListView.java:3234)
at android.view.View.dispatchTouchEvent(View.java:3709)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
[...]

帮助?不再需要,请参见下面

最终答案:事实证明,我每 5 次插入就调用 notifyDataSetChanged避免闪烁和突然的列表更改。不能以这种方式完成,当基本列表更改时始终通知适配器。这个错误对我来说早已消失了。

What I want to do: run a background thread which calculates ListView contents and update ListView partially, while results are calculated.

What I know I have to avoid: I cannot mess with ListAdapter contents from background thread, so I inherited AsyncTask and publish result (add entries to adapter) from onProgressUpdate. My Adapter uses ArrayList of result objects, all operations on those arraylists are synchronized.

Research of other people: there is very valuable data here. I also suffered from almost daily crashes for group of ~500 users, and when I added list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) block in onProgressUpdate, crashes lowered by a factor of 10 but not disappeared. (it was suggested in answer )

What I got sometimes: please notice, it happens really rarely (once a week for one of 3.5k users). But I'd like to get rid of this bug completely. Here is partial stacktrace:

`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. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1432)
at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)
at android.widget.ListView.onTouchEvent(ListView.java:3234)
at android.view.View.dispatchTouchEvent(View.java:3709)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
[...]

Help? Not needed anymore, see below

FINAL ANSWER: As it turned out, I was calling notifyDataSetChanged every 5 insertions to avoid flickering and sudden list changes. It cannot be done such way, always notify adapter when base list changes. This bug it long gone for me now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(25

忘东忘西忘不掉你 2024-09-14 19:48:07

我有同样的问题。

我在 UI 线程之外向我的 ArrayList 添加项目。

解决方案:我已经完成了这两件事,添加项目并在 UI 线程中调用notifyDataSetChanged()

I had the same issue.

I was adding items to my ArrayList outside the UI thread.

Solution: I have done both, adding the items and called notifyDataSetChanged() in the UI thread.

郁金香雨 2024-09-14 19:48:07

方法修复了它

requestLayout();

我遇到了同样的问题,但我使用类 ListView 中的

I had the same problem, but I fixed it using the method

requestLayout();

from the class ListView

孤寂小茶 2024-09-14 19:48:07

这是一个多线程问题,使用正确的同步块可以避免这种情况。
不会在 UI 线程上放置额外的东西并导致应用程序响应能力下降。

我也面临同样的情况。正如最受接受的答案所示,从 UI 线程更改适配器数据可以解决该问题。这会起作用,但这是一种快速而简单的解决方案,但不是最好的解决方案。

正如您在正常情况下所看到的。从后台线程更新数据适配器并在 UI 线程中调用 notificationDataSetChanged 是可行的。

当 ui 线程正在更新视图并且另一个后台线程再次更改数据时,会出现此非法状态异常。那一刻导致了这个问题。

因此,如果您要同步更改适配器数据并进行notifydatasetchange 调用的所有代码。这个问题应该消失了。对我来说已经过去了,我仍在更新后台线程的数据。

这是我的具体案例代码,供其他人参考。

主屏幕上的加载程序将电话簿联系人加载到后台的数据源中。

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

此 PhoneBookManager.getPhoneBookContacts 从电话簿中读取联系人并将其填充到哈希图中。可以直接使用列表适配器来绘制列表。

我的屏幕上有一个按钮。这将打开一个列出这些电话号码的活动。
如果我在前一个线程完成其工作之前直接在列表上设置适配器,那么快速导航的情况就会很少发生。它弹出异常。这是这个问题的标题。所以我必须在第二个活动中做这样的事情。

我在第二个活动中的加载程序等待第一个线程完成。直到显示进度条。检查两个加载器的 loadInBackground 。

然后它创建适配器并将其传递给我在 ui 线程上调用 setAdapter 的活动。

这解决了我的问题。

此代码只是一个片段。您需要更改它才能为您顺利编译。

@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
    return new PhoneBookContactLoader(this);
}

@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
    contactList.setAdapter(adapter = arg1);
}

/*
 * AsyncLoader to load phonebook and notify the list once done.
 */
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {

    private PhoneBookContactAdapter adapter;

    public PhoneBookContactLoader(Context context) {
        super(context);
    }

    @Override
    public PhoneBookContactAdapter loadInBackground() {
        synchronized (SingleTonProvider.getInstance()) {
            return adapter = new PhoneBookContactAdapter(getContext());    
        }
    }

}

希望这有帮助

This is a MultiThreading Issue and Using Properly Synchronized Blocks This can be prevented.
Without putting extra things on UI Thread and causing loss of responsiveness of app.

I also faced the same. And as the most accepted answer suggests making change to adapter data from UI Thread can solve the issue. That will work but is a quick and easy solution but not the best one.

As you can see for a normal case. Updating data adapter from background thread and calling notifyDataSetChanged in UI thread works.

This illegalStateException arises when a ui thread is updating the view and another background thread changes the data again. That moment causes this issue.

So if you will synchronize all the code which is changing the adapter data and making notifydatasetchange call. This issue should be gone. As gone for me and i am still updating the data from background thread.

Here is my case specific code for others to refer.

My loader on the main screen loads the phone book contacts into my data sources in the background.

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

This PhoneBookManager.getPhoneBookContacts reads contact from phonebook and fills them in the hashmaps. Which is directly usable for List Adapters to draw list.

There is a button on my screen. That opens a activity where these phone numbers are listed.
If i directly setAdapter over the list before the previous thread finishes its work which is fast naviagtion case happens less often. It pops up the exception .Which is title of this SO question. So i have to do something like this in the second activity.

My loader in the second activity waits for first thread to complete. Till it shows a progress bar. Check the loadInBackground of both the loaders.

Then it creates the adapter and deliver it to the activity where on ui thread i call setAdapter.

That solved my issue.

This code is a snippet only. You need to change it to compile well for you.

@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
    return new PhoneBookContactLoader(this);
}

@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
    contactList.setAdapter(adapter = arg1);
}

/*
 * AsyncLoader to load phonebook and notify the list once done.
 */
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {

    private PhoneBookContactAdapter adapter;

    public PhoneBookContactLoader(Context context) {
        super(context);
    }

    @Override
    public PhoneBookContactAdapter loadInBackground() {
        synchronized (SingleTonProvider.getInstance()) {
            return adapter = new PhoneBookContactAdapter(getContext());    
        }
    }

}

Hope this helps

ˉ厌 2024-09-14 19:48:07

我通过两个列表解决了这个问题。我仅将一个列表用于适配器,并在另一个列表上进行所有数据更改/更新。这允许我在后台线程中更新一个列表,然后更新主/UI 线程中的“适配器”列表:

List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();

...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);

// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            // Make updates the "data" list.
            ...

            // Update your adapter.
            refreshList();
        }
    }).start();
}

void refreshList()
{
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            adapterData.clear();
            adapterData.addAll(data);
            adapter.notifyDataSetChanged();
            listView.invalidateViews();
        }
    });
}

I solved this by have 2 Lists. One list I use for only the adapter, and I do all data changes/updates on the other list. This allows me to do updates on one list in a background thread, and then update the "adapter" list in the main/UI thread:

List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();

...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);

// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            // Make updates the "data" list.
            ...

            // Update your adapter.
            refreshList();
        }
    }).start();
}

void refreshList()
{
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            adapterData.clear();
            adapterData.addAll(data);
            adapter.notifyDataSetChanged();
            listView.invalidateViews();
        }
    });
}
好听的两个字的网名 2024-09-14 19:48:07

我编写了这段代码,并让它在 2.1 模拟器映像中运行了约 12 小时,并且没有收到 IllegalStateException。我将给予 Android 框架一个怀疑的好处,并说这很可能是您的代码中的一个错误。我希望这有帮助。也许您可以根据您的列表和数据调整它。

public class ListViewStressTest extends ListActivity {
    ArrayAdapter<String> adapter;
    ListView list;
    AsyncTask<Void, String, Void> task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.list = this.getListView();

        this.list.setAdapter(this.adapter);

        this.task = new AsyncTask<Void, String, Void>() {
            Random r = new Random();
            int[] delete;
            volatile boolean scroll = false;

            @Override
            protected void onProgressUpdate(String... values) {
                if(scroll) {
                    scroll = false;
                    doScroll();
                    return;
                }

                if(values == null) {
                    doDelete();
                    return;
                }

                doUpdate(values);

                if(ListViewStressTest.this.adapter.getCount() > 5000) {
                    ListViewStressTest.this.adapter.clear();
                }
            }

            private void doScroll() {
                if(ListViewStressTest.this.adapter.getCount() == 0) {
                    return;
                }

                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
                ListViewStressTest.this.list.setSelection(n);
            }

            private void doDelete() {
                int[] d;
                synchronized(this) {
                    d = this.delete;
                }
                if(d == null) {
                    return;
                }
                for(int i = 0 ; i < d.length ; i++) {
                    int index = d[i];
                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
                    }
                }
            }

            private void doUpdate(String... values) {
                for(int i = 0 ; i < values.length ; i++) {
                    ListViewStressTest.this.adapter.add(values[i]);
                }
            }

            private void updateList() {
                int number = r.nextInt(30) + 1;
                String[] strings = new String[number];

                for(int i = 0 ; i < number ; i++) {
                    strings[i] = Long.toString(r.nextLong());
                }

                this.publishProgress(strings);
            }

            private void deleteFromList() {
                int number = r.nextInt(20) + 1;
                int[] toDelete = new int[number];

                for(int i = 0 ; i < number ; i++) {
                    int num = ListViewStressTest.this.adapter.getCount();
                    if(num < 2) {
                        break;
                    }
                    toDelete[i] = r.nextInt(num);
                }

                synchronized(this) {
                    this.delete = toDelete;
                }

                this.publishProgress(null);
            }

            private void scrollSomewhere() {
                this.scroll = true;
                this.publishProgress(null);
            }

            @Override
            protected Void doInBackground(Void... params) {
                while(true) {
                    int what = r.nextInt(3);

                    switch(what) {
                        case 0:
                            updateList();
                            break;
                        case 1:
                            deleteFromList();
                            break;
                        case 2:
                            scrollSomewhere();
                            break;
                    }

                    try {
                        Thread.sleep(0);
                    } catch(InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        };

        this.task.execute(null);
    }
}

I wrote this code and had it run in a 2.1 emulator image for ~12 hours and did not get the IllegalStateException. I'm going to give the android framework the benefit of the doubt on this one and say that it is most likely an error in your code. I hope this helps. Maybe you can adapt it to your list and data.

public class ListViewStressTest extends ListActivity {
    ArrayAdapter<String> adapter;
    ListView list;
    AsyncTask<Void, String, Void> task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.list = this.getListView();

        this.list.setAdapter(this.adapter);

        this.task = new AsyncTask<Void, String, Void>() {
            Random r = new Random();
            int[] delete;
            volatile boolean scroll = false;

            @Override
            protected void onProgressUpdate(String... values) {
                if(scroll) {
                    scroll = false;
                    doScroll();
                    return;
                }

                if(values == null) {
                    doDelete();
                    return;
                }

                doUpdate(values);

                if(ListViewStressTest.this.adapter.getCount() > 5000) {
                    ListViewStressTest.this.adapter.clear();
                }
            }

            private void doScroll() {
                if(ListViewStressTest.this.adapter.getCount() == 0) {
                    return;
                }

                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
                ListViewStressTest.this.list.setSelection(n);
            }

            private void doDelete() {
                int[] d;
                synchronized(this) {
                    d = this.delete;
                }
                if(d == null) {
                    return;
                }
                for(int i = 0 ; i < d.length ; i++) {
                    int index = d[i];
                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
                    }
                }
            }

            private void doUpdate(String... values) {
                for(int i = 0 ; i < values.length ; i++) {
                    ListViewStressTest.this.adapter.add(values[i]);
                }
            }

            private void updateList() {
                int number = r.nextInt(30) + 1;
                String[] strings = new String[number];

                for(int i = 0 ; i < number ; i++) {
                    strings[i] = Long.toString(r.nextLong());
                }

                this.publishProgress(strings);
            }

            private void deleteFromList() {
                int number = r.nextInt(20) + 1;
                int[] toDelete = new int[number];

                for(int i = 0 ; i < number ; i++) {
                    int num = ListViewStressTest.this.adapter.getCount();
                    if(num < 2) {
                        break;
                    }
                    toDelete[i] = r.nextInt(num);
                }

                synchronized(this) {
                    this.delete = toDelete;
                }

                this.publishProgress(null);
            }

            private void scrollSomewhere() {
                this.scroll = true;
                this.publishProgress(null);
            }

            @Override
            protected Void doInBackground(Void... params) {
                while(true) {
                    int what = r.nextInt(3);

                    switch(what) {
                        case 0:
                            updateList();
                            break;
                        case 1:
                            deleteFromList();
                            break;
                        case 2:
                            scrollSomewhere();
                            break;
                    }

                    try {
                        Thread.sleep(0);
                    } catch(InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        };

        this.task.execute(null);
    }
}
怪我入戏太深 2024-09-14 19:48:07

前几天我也遇到了同样的问题,每天都会造成几千次崩溃,大约0.1%的用户遇到这种情况。我尝试了 setVisibility(GONE/VISIBLE) 和 requestLayout() ,但崩溃次数仅减少了一点。

我终于解决了它。 setVisibility(GONE/VISIBLE) 没有任何作用。 requestLayout() 没有任何内容。

最后我发现原因是我在更新数据后使用 Handler 调用 notifyDataSetChanged() ,这可能会导致一种:

  1. 将数据更新到模型对象(我调用它是一个数据源)
  2. 用户触摸listview(可能会调用checkForTap()/onTouchEvent()并最终调用layoutChildren()
  3. 适配器从模型对象并调用notifyDataSetChanged()并更新视图

我在getCount()getItem()getView中犯了另一个错误(),我直接使用DataSource中的字段,而不是将它们复制到适配器中。因此,最终它会在以下情况下崩溃:

  1. 适配器更新上次响应给出的数据
  2. 当下一个响应返回时,DataSource 更新数据,这会导致项目计数发生变化
  3. 用户触摸列表视图,这可能是点击、移动或翻转
  4. getCount() 并且调用了getView(),listview发现数据不一致,并抛出类似java.lang.IllegalStateException: The content of theadapter haschangebut...的异常>。如果您在 ListView 中使用页眉/页脚,另一个常见的异常是 IndexOutOfBoundException

所以解决方案很简单,当我的处理程序触发适配器获取数据并调用 notifyDataSetChanged() 时,我只需将数据从我的数据源复制到适配器即可。现在,事故再也不会发生了。

Several days ago I met the same problem and causes several thousands of crash per day, about 0.1% of users meet this situation. I tried setVisibility(GONE/VISIBLE) and requestLayout(), but crash count only decreases a little.

And I finally solved it. Nothing with setVisibility(GONE/VISIBLE). Nothing with requestLayout().

Finally I found the reason is I used a Handler to call notifyDataSetChanged() after update data, which may lead to a sort of:

  1. Updates data to a model object(I call it a DataSource)
  2. User touches listview(which may call checkForTap()/onTouchEvent() and finally calls layoutChildren() )
  3. Adapter gets data from model object and call notifyDataSetChanged() and update views

And I made another mistake that in getCount(), getItem() and getView(), I directly use fields in DataSource, rather than copy them to the adapter. So finally it crashes when:

  1. Adapter updates data which last response gives
  2. When next response back, DataSource updates data, which causes item count change
  3. User touches listview, which may be a tap or a move or flip
  4. getCount() and getView() is called, and listview finds data is not consistent, and throws exceptions like java.lang.IllegalStateException: The content of the adapter has changed but.... Another common exception is an IndexOutOfBoundException if you use header/footer in ListView.

So solution is easy, I just copy data to adapter from my DataSource when my Handler triggers adapter to get data and calls notifyDataSetChanged(). The crash now never happens again.

蘸点软妹酱 2024-09-14 19:48:07

如果这种情况间歇性地发生,结果证明我只有在单击“加载更多”最后一个项目后滚动列表时才遇到此问题。如果列表没有滚动,则一切正常。

经过多次调试,这是我的一个错误,但 Android 代码也不一致。

当验证发生时,此代码在 ListView 中执行

        } 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 "

,但是当 onChange 发生时,它会在 AdapterView(ListView 的父级)中触发此代码

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

注意适配器不保证相同!< /em>

就我而言,因为它是一个“LoadMoreAdapter”,所以我在 getAdapter 调用中返回 WrappedAdapter(用于访问底层对象)。由于额外的“加载更多”项目和抛出异常,这导致计数不同。

我这样做只是因为文档使它看起来可以做

ListView.getAdapter javadoc

返回当前在此 ListView 中使用的适配器。返回的
适配器可能与传递给的适配器不同
setAdapter(ListAdapter) 但可能是一个 WrapperListAdapter。

Had this happen intermittently, turns out I only had this issue when the list was scrolled after a 'load more' last item was clicked. If the list wasn't scrolled, everything worked fine.

After MUCH debugging, it was a bug on my part, but an inconsistency in the Android code also.

When the validation happens, this code is executed in ListView

        } 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 "

But when onChange happens it fires this code in AdapterView (parent of ListView)

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

Notice the way the Adapter is NOT guaranteed to be the Same!

In my case, since it was a 'LoadMoreAdapter' I was returning the WrappedAdapter in the getAdapter call (for access to the underlying objects). This resulted in the counts being different due to the extra 'Load More' item and the Exception being thrown.

I only did this because the docs make it seem like it's ok to do

ListView.getAdapter javadoc

Returns the adapter currently in use in this ListView. The returned
adapter might not be the same adapter passed to
setAdapter(ListAdapter) but might be a WrapperListAdapter.

江湖正好 2024-09-14 19:48:07

我的问题与使用 Filter

当设置或更新 ListView 的底层数据模型时,我做了这样的事情:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

在最后一行调用 filter() 将(并且必须)导致 notifyDataSetChanged()在过滤器的 publishResults() 方法中调用。有时这可能工作正常,特别是在我的快速 Nexus 5 中。但实际上,它隐藏了一个错误,您会在速度较慢的设备或资源密集型条件下注意到该错误。

问题在于过滤是异步完成的,因此在 UI 线程中的 filter() 语句结束和对 publishResults() 的调用之间,一些其他 UI 线程代码可能会执行并更改适配器的内容。

实际的修复很简单,只需在请求执行过滤之前调用 notifyDataSetChanged() 即可:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}

My issue was related to the use of a Filter together with the ListView.

When setting or updating the underlying data model of the ListView, I was doing something like this:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

Calling filter() in the last line will (and must) cause notifyDataSetChanged() to be called in the Filter's publishResults() method. This may work okay sometimes, specially in my fast Nexus 5. But in reality, it's hiding a bug that you will notice with slower devices or in resource intensive conditions.

The problem is that the filtering is done asynchronously, and thus between the end of the filter() statement and the call to publishResults(), both in the UI thread, some other UI thread code may execute and change the content of the adapter.

The actual fix is easy, just call notifyDataSetChanged() also before requesting the filtering to be performed:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
难如初 2024-09-14 19:48:07

我有一个列表 if Feed 对象。
它是从非 UI 线程追加和截断的。
它与下面的适配器配合得很好。
无论如何,我会在 UI 线程中调用 FeedAdapter.notifyDataSetChanged,但稍后会调用。
我确实喜欢这个,因为即使 UI 已死,我的 Feed 对象仍保留在本地服务的内存中。

public class FeedAdapter extends BaseAdapter {
    private int size = 0;
    private final List<Feed> objects;

    public FeedAdapter(Activity context, List<Feed> objects) {
        this.context = context;
        this.objects = objects;
        size = objects.size();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ...
    }

    @Override
    public void notifyDataSetChanged() {
        size = objects.size();

        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return size;
    }

    @Override
    public Object getItem(int position) {
        try {
            return objects.get(position);
        } catch (Error e) {
            return Feed.emptyFeed;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}

I have a List if Feed objects.
It's appended and truncated from none-UI thread.
It works fine with adapter below.
I call FeedAdapter.notifyDataSetChanged in UI thread anyway but little bit later.
I do like this because my Feed objects stay in memory in Local Service even when UI is dead.

public class FeedAdapter extends BaseAdapter {
    private int size = 0;
    private final List<Feed> objects;

    public FeedAdapter(Activity context, List<Feed> objects) {
        this.context = context;
        this.objects = objects;
        size = objects.size();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ...
    }

    @Override
    public void notifyDataSetChanged() {
        size = objects.size();

        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return size;
    }

    @Override
    public Object getItem(int position) {
        try {
            return objects.get(position);
        } catch (Error e) {
            return Feed.emptyFeed;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}
菩提树下叶撕阳。 2024-09-14 19:48:07

我遇到了同样的问题,错误日志完全相同。
在我的例子中,AsyncTask 的 onProgress() 使用 mAdapter.add(newEntry) 将值添加到适配器。为了避免 UI 响应速度降低,我设置了 mAdapter.setNotifyOnChange(false) 并每秒调用 mAdapter.notifyDataSetChanged() 4 次。每秒对数组进行一次排序。

这个效果很好,看起来很容易上瘾,但不幸的是,如果经常触摸显示的列表项,可能会导致它崩溃。

但我似乎找到了一个可以接受的解决方法。
我猜想,即使您只在 ui 线程上工作,适配器也不会在不调用 notifyDataSetChanged() 的情况下接受对其数据的许多更改,因此我创建了一个存储所有新项目的队列直到提到的300ms结束。如果到达这一时刻,我会一次性添加所有存储的项目并调用 notifyDataSetChanged()
到目前为止,我无法再使列表崩溃

I'm was facing the same problem with exactly the same error log.
In my case onProgress() of the AsyncTask adds the values to the adapter using mAdapter.add(newEntry). To avoid the UI becoming less responsive I set mAdapter.setNotifyOnChange(false) and call mAdapter.notifyDataSetChanged() 4 times the second. Once per second the array is sorted.

This work well and looks very addictive, but unfortunately it is possible to crash it by touching the shown list items often enough.

But it seems I have found an acceptable workaround.
My guess it that even if you just work on the ui thread the adapter does not accept many changes to it's data without calling notifyDataSetChanged(), because of this I created a queue that is storing all the new items until the mentioned 300ms are over. If this moment is reached I add all the stored items in one shot and call notifyDataSetChanged().
Until now I was not able to crash the list anymore.

神经大条 2024-09-14 19:48:07

这是 Android 4 到 4.4(KitKat) 中的一个已知错误,已在“>4.4”中解决。

请参阅此处:https://code.google.com/p/android/issues/detail?id=71936

This is a known bug in Android 4 to 4.4(KitKat) and is resolved in ">4.4"

See here: https://code.google.com/p/android/issues/detail?id=71936

仅冇旳回忆 2024-09-14 19:48:07

即使我在 XMPP 通知应用程序中遇到了同样的问题,接收者消息也需要添加回列表视图(使用 ArrayList 实现)。当我尝试通过 MessageListener (单独的线程)添加接收器内容时,应用程序因上述错误而退出。我通过将内容添加到我的 arraylist & 中解决了这个问题。 setListviewadapater 通过 Activity 类的 runOnUiThread 方法。这解决了我的问题。

Even I faced the same problem in my XMPP notification application, receivers message needs to be added back to list view (implemented with ArrayList). When I tried to add the receiver content through MessageListener (separate thread), application quits with above error. I solved this by adding the content to my arraylist & setListviewadapater through runOnUiThread method which is part of Activity class. This solved my problem.

花开半夏魅人心 2024-09-14 19:48:07

我遇到了类似的问题,这就是我在我的案例中解决的方法。我验证 task 是否已处于 RUNNINGFINISHED 状态,因为任务只能运行一次。下面您将看到我的解决方案的部分和改编代码。

public class MyActivity... {
    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // your code
       task = new MyTask();
       setList();
    }

    private void setList() {
    if (task != null)
        if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
            task.cancel(true);
            task = new MyTask();
            task.execute();         
        } else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
            task = new MyTask();
            task.execute();
        } else 
            task.execute();
    }

    class MyTask extends AsyncTask<Void, Item, Void>{
       List<Item> Itens;

       @Override
       protected void onPreExecute() {

        //your code

        list.setVisibility(View.GONE);
        adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
        list.setAdapter(adapterItem);

        adapterItem.notifyDataSetChanged();
    }

    @Override
    protected Void doInBackground(Void... params) {

        Itens = getItens();
        for (Item item : Itens) {
            publishProgress(item );
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Item ... item ) {           
        adapterItem.add(item[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        //your code
        adapterItem.notifyDataSetChanged();     
        list.setVisibility(View.VISIBLE);
    }

}

}

I faced a similar problem, here's how I solved in my case. I verify if the task already is RUNNING or FINISHED because an task can run only once. Below you will see a partial and adapted code from my solution.

public class MyActivity... {
    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // your code
       task = new MyTask();
       setList();
    }

    private void setList() {
    if (task != null)
        if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
            task.cancel(true);
            task = new MyTask();
            task.execute();         
        } else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
            task = new MyTask();
            task.execute();
        } else 
            task.execute();
    }

    class MyTask extends AsyncTask<Void, Item, Void>{
       List<Item> Itens;

       @Override
       protected void onPreExecute() {

        //your code

        list.setVisibility(View.GONE);
        adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
        list.setAdapter(adapterItem);

        adapterItem.notifyDataSetChanged();
    }

    @Override
    protected Void doInBackground(Void... params) {

        Itens = getItens();
        for (Item item : Itens) {
            publishProgress(item );
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Item ... item ) {           
        adapterItem.add(item[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        //your code
        adapterItem.notifyDataSetChanged();     
        list.setVisibility(View.VISIBLE);
    }

}

}
固执像三岁 2024-09-14 19:48:07

我有同样的问题,我解决了。我的问题是我使用的是 listview,带有数组适配器和过滤器。在方法 performFiltering 上,我弄乱了包含数据的数组,这是问题所在,因为该方法不在 UI 线程上运行,最终会引发一些问题。

I had the same problem and I solved it. My problem was that I was using a listview, with an array adapter and with filter. On the method performFiltering I was messing with the array that have the data and it was the problem since this method is not running on the UI thread and EVENTUALLY it raises some problems.

楠木可依 2024-09-14 19:48:07

导致此崩溃的原因之一是 ArrayList 对象无法完全更改。
因此,当我删除一个项目时,我必须这样做:

mList.clear();
mList.addAll(newDataList);

这为我解决了崩溃问题。

One cause for this crash is that ArrayList object cannot change completely.
So, when I remove an item, I have to do this:

mList.clear();
mList.addAll(newDataList);

This fixed the crash for me.

凉薄对峙 2024-09-14 19:48:07

就我而言,我从主 Activity 上的 TextWatcher() 方法调用了适配器上的方法 GetFilter(),并在 上使用 For 循环添加了数据GetFilter()。
解决方案是将 For 循环更改为主 Activity 上的 AfterTextChanged() 子方法,并删除对 GetFilter() 的调用

In my case I called the method GetFilter() on an adapter from the TextWatcher() method on main Activity, and I added the data with a For loop on GetFilter().
The solution was change the For loop to AfterTextChanged() sub method on main Activity and delete the call to GetFilter()

音盲 2024-09-14 19:48:07
adapter.notifyDataSetChanged()
adapter.notifyDataSetChanged()
酒绊 2024-09-14 19:48:07

我也遇到了完全相同的错误并使用 AsyncTask :

`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. [in ListView(2131296334, class android.widget.ListView) with Adapter... etc

我通过将 adapter.notifyDataSetChanged(); 放在 UI 线程的底部(即我的 AsyncTask onPostExecute 方法)来解决它。像这样:

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

现在我的应用程序可以运行了。

编辑:事实上,我的应用程序仍然每 10 次就有 1 次崩溃,并给出相同的错误。

最终我在上一篇文章中遇到了 runOnUiThread,我认为这可能是有用。所以我把它放在我的 doInBackground 方法中,如下所示:

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

我删除了 adapter.notifyDataSetChanged(); 方法。现在,我的应用程序永远不会崩溃。

I was also getting exact same error and using AsyncTask :

`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. [in ListView(2131296334, class android.widget.ListView) with Adapter... etc

I solved it by puttingadapter.notifyDataSetChanged(); at the bottom of my UI thread, that is my AsyncTask onPostExecute method. Like this :

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

Now my app works.

EDIT : In fact, my app still crashed about every 1 in 10 times, giving the same error.

Eventually I came across runOnUiThread on a previous post, which I thought could be useful. So I put it in my doInBackground method, like this :

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

And I removed the adapter.notifyDataSetChanged(); method. Now, my app never crashes.

坐在坟头思考人生 2024-09-14 19:48:07

请尝试以下解决方案之一:

  1. 有时,如果您在线程中向数据列表添加新对象(或doInBackground方法),则会发生此错误。解决方案是:创建一个临时列表并在线程(或 doInBackground)中将数据添加到该列表,然后将临时列表中的所有数据复制到 UI 线程中的适配器列表(或 onPostExcute)

  2. 确保所有 UI 更新都在 UI 线程中调用。

Please try one of these solutions :

  1. Sometimes, if you add new object to data list in a thread (or doInBackground method), this error will occur. The solution is : create a temporary list and do adding data to this list in thread(or doInBackground), then do copying all data from temporary list to the list of adapter in UI thread (or onPostExcute)

  2. Make sure all UI updates are called in UI thread.

天冷不及心凉 2024-09-14 19:48:07

在惰性图像加载器中添加新数据时我遇到了同样的问题
我只是

         adapter.notifyDataSetChanged();

希望

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

它能帮助你

i haved same problem when add new data in lazy image loader
i just put

         adapter.notifyDataSetChanged();

in

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

hope it helps you

情泪▽动烟 2024-09-14 19:48:07

就像@Mullins 所说的“
我都添加了这些项目并在 UI 线程中调用了 notifyDataSetChanged() 并且解决了这个问题。 – Mullins”。

在我的例子中,我有 asynctask 并且我在 doInBackground() 方法中调用了 notifyDataSetChanged() ,问题就解决了,当我从 onPostExecute() 调用我收到了异常。

Like @Mullins said "
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins".

In my case I have asynctask and I called notifyDataSetChanged() in the doInBackground() method and the problem is solved, when I called from onPostExecute() I received the exception.

我还不会笑 2024-09-14 19:48:07

我有一个自定义 ListAdapter 并在方法的开头而不是结尾处调用 super.notifyDataSetChanged()

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}

I had a custom ListAdapter and was calling super.notifyDataSetChanged() at the beginning and not the end of the method

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
奢华的一滴泪 2024-09-14 19:48:07

我也有同样的情况,我在列表视图上的项目中有很多按钮组,并且我正在更改项目中的一些布尔值,例如holder.rbVar.setOnclik...

我的问题发生是因为我在 getView(); 中调用了一个方法;并在 sharepreference 中保存一个对象,所以我在上面

如何解决它时遇到了同样的错误;我删除了 getView() 中的方法来通知DataSetInvalidated(),问题就消失了

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }

I had the same sittuation , I had many buttongroup insite my item on listview and I was changing some boolean values inside my item like holder.rbVar.setOnclik...

my problem occured because I was calling a method inside getView(); and was saving an object inside sharepreference, so I had same error above

How I solved it; I removed my method inside getView() to notifyDataSetInvalidated() and problem gone

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
请别遗忘我 2024-09-14 19:48:07

我有同样的问题。最后我在更新列表视图之前得到了解决方案

,如果存在软键盘,请先将其关闭。之后设置数据源并调用notifydatasetchanged()。

在内部关闭键盘时,列表视图将更新其用户界面。它会一直呼叫直到关闭键盘。那个时候如果数据源改变就会抛出这个异常。
如果数据在 onActivityResult 中更新,则有可能出现相同的错误。

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);

i had the same problem. finally i got the solution

before updating listview, if the soft keypad is present close it first. after that set data source and call notifydatasetchanged().

while closing keypad internally listview will update its ui. it keep calling till closing keypad. that time if data source change it willl throw this exception.
if data is updating in onActivityResult, there is a chance for same error.

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);
蒗幽 2024-09-14 19:48:07

我的解决方案:

1)创建一个临时ArrayList。

2)在 doInBackground 方法中完成繁重的工作(sqlite row fetch 等)并将项目添加到临时数组列表中。

3)在 onPostExecute 方法中将临时数组列表中的所有项目添加到列表视图的数组列表中。

注意:您可能想要从 listview 中删除一些项目,并从 sqlite 数据库中删除,并且可能从 sdcard 中删除与项目相关的一些文件,只需从数据库中删除项目并删除其相关文件并将它们添加到 temp 后台线程中的arraylist。然后在 UI 线程中从列表视图的数组列表中删除临时数组列表中存在的项目。

希望这有帮助。

My solution:

1) create a temp ArrayList.

2) do your heavy works (sqlite row fetch , ...) in doInBackground method and add items to the temp arraylist.

3) add all items from temp araylist to your listview's arraylist in onPostExecute method.

note: you may want to delete some items from listview and also delete from sqlite database and maybe delete some files related to items from sdcard , just remove items from database and remove their related files and add them to temp arraylist in background thread. then in UI thread delete items existing in temp arraylist from the listview's arraylist.

Hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文