Android, ListView IllegalStateException: «Содержимое адаптера изменилось, но ListView не получил уведомление» - PullRequest
184 голосов
/ 28 июня 2010

Что я хочу сделать : запустить фоновый поток, который вычисляет содержимое ListView и частично обновить ListView, пока рассчитываются результаты.

Чего я знаю, что мне следует избегать : я не могу связываться с содержимым ListAdapter из фонового потока, поэтому я унаследовал AsyncTask и опубликовал результат (добавление записей в адаптер) из onProgressUpdate.Мой адаптер использует ArrayList объектов результатов, все операции над этими массивами синхронизированы.

Исследования других людей : здесь есть очень ценные данные здесь .Я также страдал от почти ежедневных сбоев для группы из ~ 500 пользователей, и когда я добавил блок list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) в onProgressUpdate, сбои уменьшились в 10 раз, но не исчезли.(это было предложено в ответ )

То, что я иногда получал : пожалуйста, обратите внимание, что это происходит очень редко (один раз в неделю для одного из пользователей 3.5k).Но я бы хотел полностью избавиться от этой ошибки.Вот частичная трассировка стека:

`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)
[...]

Справка? Больше не нужна, см. Ниже

ЗАКЛЮЧИТЕЛЬНЫЙ ОТВЕТ: Как оказалось, я былвызывая notifyDataSetChanged каждые 5 вставок, чтобы избежать мерцания и внезапных изменений списка.Это не может быть сделано таким образом, всегда уведомляйте адаптер при изменении базового списка.Эта ошибка давно исчезла для меня.

Ответы [ 24 ]

2 голосов
/ 14 февраля 2011

Даже если я столкнулся с той же проблемой в моем приложении уведомлений XMPP, сообщение получателей необходимо добавить обратно в список (реализовано с помощью ArrayList). Когда я попытался добавить содержимое получателя через MessageListener (отдельный поток), приложение закрывается с ошибкой выше. Я решил эту проблему, добавив содержимое в мой метод arraylist & setListviewadapater - runOnUiThread, который является частью класса Activity. Это решило мою проблему.

2 голосов
/ 18 апреля 2011

Я столкнулся с той же проблемой с точно таким же журналом ошибок.В моем случае onProgress() AsyncTask добавляет значения к адаптеру, используя mAdapter.add(newEntry).Чтобы пользовательский интерфейс не стал менее отзывчивым, я установил mAdapter.setNotifyOnChange(false) и позвонил mAdapter.notifyDataSetChanged() 4 раза в секунду.Раз в секунду массив сортируется.

Это хорошо работает и выглядит очень затягивающе, но, к сожалению, его можно разбить, если достаточно часто касаться показанных элементов списка.кажется, я нашел приемлемый обходной путь. Полагаю, даже если вы просто работаете с потоком пользовательского интерфейса, адаптер не принимает много изменений в своих данных без вызова notifyDataSetChanged(), поэтому я создал очередь, в которой хранитсявсе новые предметы до тех пор, пока не закончится 300 мс.Если этот момент достигнут, я добавляю все сохраненные предметы в один выстрел и вызываю notifyDataSetChanged().До сих пор я не мог больше рушить список .

1 голос
/ 09 июня 2016

Я столкнулся с подобной проблемой, вот как я решил в моем случае. Я проверяю, является ли task уже RUNNING или FINISHED, потому что задача может быть запущена только один раз. Ниже вы увидите частичный и адаптированный код из моего решения.

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);
    }

}

}
1 голос
/ 30 апреля 2013

В моем случае я вызвал метод GetFilter() на адаптере из метода TextWatcher() в основном Activity и добавил данные с помощью цикла For на GetFilter(). Решением было изменить цикл For на AfterTextChanged() sub-метод на главной Activity и удалить вызов на GetFilter()

1 голос
/ 09 марта 2011

У меня была такая же проблема, и я решил ее.Моя проблема заключалась в том, что я использовал listview, с адаптером массива и с фильтром.В методе performFiltering я возился с массивом, в котором есть данные, и это была проблема, поскольку этот метод не работает в потоке пользовательского интерфейса и в конечном итоге вызывает некоторые проблемы.

1 голос
/ 28 марта 2013

Одной из причин этого сбоя является то, что объект ArrayList не может полностью измениться. Итак, когда я удаляю предмет, я должен сделать это:

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

Это исправило падение для меня.

0 голосов
/ 11 февраля 2017

У меня была та же самая ситуационная ситуация, у меня было много buttongroup, инстинктирующих мой элемент при просмотре списка, и я изменял некоторые логические значения внутри моего элемента, как holder.rbVar.setOnclik ...

моя проблема произошла, потому что я вызывал метод внутри getView (); и сохранял объект внутри sharepreference, поэтому у меня была та же ошибка выше

Как я это решил; Я удалил свой метод внутри getView () для notifyDataSetInvalidated () и проблема исчезла

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
0 голосов
/ 09 мая 2016

Я также получаю точно такую ​​же ошибку и использую 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(); внизу моего потока пользовательского интерфейса, то есть мой метод AsyncTask onPostExecute. Как это:

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

Теперь мое приложение работает.

РЕДАКТИРОВАТЬ: На самом деле мое приложение по-прежнему зависало примерно 1 раз в 10 раз, выдавая ту же ошибку. В конце концов я наткнулся на runOnUiThread в предыдущем посте, который, как мне показалось, может быть полезен. Поэтому я поместил его в свой метод doInBackground, например так:

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

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

И я удалил adapter.notifyDataSetChanged(); метод. Теперь мое приложение никогда не падает.

0 голосов
/ 09 ноября 2015

У меня был пользовательский ListAdapter, и он вызывал super.notifyDataSetChanged() в начале, а не в конце метода

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
0 голосов
/ 11 декабря 2014

У меня возникла такая же проблема при добавлении новых данных в загрузчик ленивых изображений я просто положил

         adapter.notifyDataSetChanged();

в

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

надеюсь, это поможет вам

...