Android, ListView IllegalStateException: «Содержимое адаптера изменилось, но ListView не получил уведомление»

189

Что я хочу сделать : запустить фоновый поток, который вычисляет содержимое 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 вставок, чтобы избежать мерцания и внезапных изменений в списке. Это не может быть сделано таким образом, всегда уведомляйте адаптер при изменении базового списка. Эта ошибка давно исчезла для меня.

Томаша
источник
3
Вы вызывали notifyDataSetChanged ()?
Денис Пальницкий
1
Конечно, в onProgressUpdate идет последовательность: list.setVisibility (GONE) - addObjectToList / синхронизированная операция над списком / - notifyDataSetChanged () - list.setVisibility (VISIBLE) (и без изменений видимости исключение происходит гораздо чаще)
tomash
1
Вы модифицируете базовый ArrayList в другом потоке? Все изменения, даже в ArrayList, на который ссылается адаптер, должны происходить в потоке пользовательского интерфейса.
Рич Шулер
2
@Qberticus - как я четко сказал, я не изменяю ArrayList из другого потока, а из метода onProgressUpdate в AcyncTask - он работает в потоке GUI.
Томаш
1
@tomash я получил то же исключение, я использую pull для обновления и после выполнения я написал adaptor.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); но иногда получаю эту ошибку, как ее решить
Хан

Ответы:

119

Я была такая же проблема.

Я добавлял предметы в мой ArrayList внешний поток.

Решение: я сделал оба, adding the itemsи вызвал notifyDataSetChanged()в потоке пользовательского интерфейса.

Mullins
источник
4
Я добавил элементы и вызвал notifyDataSetChanged () в потоке пользовательского интерфейса, и решил эту проблему.
Маллинс
23
Гениальный комментарий, правда.
Дентекс
2
Это проблема многопоточности и использование правильно синхронизированных блоков. Это можно предотвратить. Не добавляя лишних вещей в UI Thread и не вызывая потери отзывчивости приложения. Проверьте мой ответ ниже.
Javanator
2
Для будущих читателей - хорошо выполнять вычисления в фоновом потоке, но вы должны передать результаты в поток пользовательского интерфейса и добавить элементы в базовую коллекцию адаптера и уведомить адаптер в одном и том же блоке кода.
Томаш
4
@Mullins Можете ли вы показать пример для вашего решения? Я застрял в той же проблеме
Jas
27

У меня была такая же проблема, но я исправил ее методом

requestLayout();

из класса ListView

gian1200
источник
35
когда мы должны вызвать этот метод?
JehandadK
3
@DeBuGGeR после добавления элементов в ListView или изменения его адаптера.
Ахмет Ноян Кызылтан
Что делать, если я добавляю элементы в asynctask
Dr. ANDRO
2
Для справки, @ Dr.aNdRO, вы собираете свои результаты в AsyncTask. doInBackground () и сохраните результаты, чтобы обновить список в AsyncTask.onPostExecute (), который будет выполняться в потоке пользовательского интерфейса. Если вам нужно обновить по мере использования, используйте AsyncTask.publishProgress () и AsyncTask.onProgressUpdate (), которые также выполняются в потоке пользовательского интерфейса.
Николь Боррелли
21

Это проблема многопоточности и использование правильно синхронизированных блоков. Это можно предотвратить. Не добавляя лишних вещей в UI Thread и не вызывая потери отзывчивости приложения.

Я тоже сталкивался с тем же. И поскольку наиболее приемлемый ответ предлагает внести изменения в данные адаптера из потока пользовательского интерфейса, можно решить эту проблему. Это будет работать, но это быстрое и простое решение, но не лучшее.

Как вы можете видеть для нормального случая. Обновление адаптера данных из фонового потока и вызов notifyDataSetChanged в потоке пользовательского интерфейса работает.

Это invalidStateException возникает, когда поток пользовательского интерфейса обновляет представление, а другой фоновый поток снова изменяет данные. Этот момент вызывает эту проблему.

Так что если вы будете синхронизировать весь код, который изменяет данные адаптера и делает вызов notifydatasetchange. Эта проблема должна исчезнуть. Как прошло для меня, и я все еще обновляю данные из фонового потока.

Вот мой конкретный код для других пользователей.

Мой загрузчик на главном экране загружает контакты телефонной книги в мои источники данных в фоновом режиме.

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

Этот PhoneBookManager.getPhoneBookContacts считывает контакты из телефонной книги и заполняет их в хэш-картах. Который непосредственно используется для списочных адаптеров для составления списка.

На моем экране есть кнопка. Это открывает деятельность, в которой перечислены эти номера телефонов. Если я непосредственно устанавливаю Adapter над списком до того, как предыдущий поток завершит свою работу, что является быстрым случаем навигации, случается реже. Появляется исключение. Это название этого ТАКОГО вопроса. Поэтому я должен сделать что-то подобное во втором упражнении.

Мой загрузчик во втором задании ожидает завершения первого потока. Пока это показывает индикатор выполнения. Проверьте loadInBackground обоих загрузчиков.

Затем он создает адаптер и доставляет его деятельности, где в потоке пользовательского интерфейса я вызываю 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());    
        }
    }

}

Надеюсь это поможет

Javanator
источник
Извини, как ты это сделал? Как вы синхронизировали код фонового потока и вызов notifydatasetchange? Я использую doInBackground при фильтрации данных в AutocompleteTextView, и я столкнулся с той же проблемой ... Не могли бы вы посоветовать мне что-нибудь для решения?
tonix
@ user3019105 - Синхронизация двух фоновых потоков. тот, который обновляет данные в фоновом режиме, и другой, который уведомляет поток пользовательского интерфейса о setAdadpter или notifydatasetchanged, используя методы handler.post или доступные методы runOnUiThread. Для моего конкретного случая я синхронизировал два загрузчика с их doInBackground, синхронизированным на одноэлементном объекте. Начните подготовку данных на самом экране-заставке, чтобы они были быстро доступны для экрана панели инструментов. Это было моей потребностью.
Javanator
Не могли бы вы опубликовать фрагмент кода этой реализации? В моем случае я решил вызвать clear () для адаптера, создав временный ArrayList <T> отфильтрованных объектов, затем вызвав addAll (tmpArrayList) и, наконец, notifyDataSetChanged (). Я делаю все эти вызовы внутри метода фильтра publishResult (), работающего в основном потоке. Как вы думаете, это надежное решение?
tonix
@ user3019105 проверьте отредактированный ответ. Надеюсь, что это полезно для вас сейчас
Javanator
этот ответ требует много читал о том, почему это работает, tutorials.jenkov.com/java-concurrency/synchronized.html это хорошее место , чтобы начать
Абду
15

Я решил это с помощью 2 списков. Один список я использую только для адаптера, и я делаю все изменения / обновления данных в другом списке. Это позволяет мне обновлять один список в фоновом потоке, а затем обновлять список «адаптеров» в главном потоке / пользовательском интерфейсе:

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();
        }
    });
}
триада
источник
7

Я написал этот код, и он работал в образе эмулятора 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);
    }
}
Рич Шулер
источник
Спасибо вам за вашу работу! Я заметил, что для метода ArrayAdapter hasStableIds () = false; моя реализация вернула true, что было не совсем верно, так как строки сортировались перед каждым notifyDataSetChanged (). Я попробую - если аварии выживут, я продолжу расследование. С уважением!
Томас
4

Несколько дней назад я столкнулся с той же самой проблемой и вызывает несколько тысяч сбоев в день, примерно 0,1% пользователей встречают эту ситуацию. Я пытался setVisibility(GONE/VISIBLE)и requestLayout(), но количество аварий только немного уменьшается.

И я наконец решил это. Ничего с setVisibility(GONE/VISIBLE). Ничего с requestLayout().

Наконец, я нашел причину в том, что я использовал Handlerдля вызова notifyDataSetChanged()после обновления данных, что может привести к некоторому виду:

  1. Обновляет данные для модельного объекта (я называю это DataSource)
  2. Пользователь касается списка просмотра (который может звонить checkForTap()/ onTouchEvent()и, наконец, звонки layoutChildren())
  3. Адаптер получает данные из объекта модели, а также вызывает notifyDataSetChanged()и обновляет представления

И я сделал еще одну ошибку , что getCount(), getItem()и getView()я непосредственно использовать поля в DataSource, а не копировать их к адаптеру. Так что, наконец, он падает, когда:

  1. Адаптер обновляет данные, которые дает последний ответ
  2. При следующем ответе DataSource обновляет данные, что приводит к изменению количества элементов.
  3. Пользователь касается просмотра списка, который может быть касанием или перемещением или отражением
  4. getCount()и getView()вызывается, и listview обнаруживает, что данные не согласованы, и выдает исключения вроде java.lang.IllegalStateException: The content of the adapter has changed but.... Другое распространенное исключение - IndexOutOfBoundExceptionесли вы используете верхний / нижний колонтитул в ListView.

Таким образом, решение легко, я просто копирую данные в адаптер из моего источника данных, когда мой обработчик запускает адаптер для получения данных и вызовов notifyDataSetChanged(). Крушение теперь никогда не случится снова.

HJWAJ
источник
1
Это была моя проблема. В моем случае getCount () возвращал новое число, доступное до вызова notifyDataSetChanged. Поскольку мой список был «виртуальным», я теперь фиксирую обновленный счетчик в своем методе notifyDataSetChanged и затем возвращаю этот кэшированный счетчик при запросе счетчика с помощью getCount ().
Гленн
3

Если бы это происходило с перебоями, оказалось, что эта проблема возникла у меня только тогда, когда список прокручивался после того, как был нажат последний элемент «загрузить еще». Если список не прокручивался, все работало нормально.

После ОЧЕНЬ отладки это была ошибка с моей стороны, но несоответствие в коде 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();

Обратите внимание, что адаптер НЕ гарантированно одинаков!

В моем случае, поскольку это был «LoadMoreAdapter», я возвращал WrappedAdapter в вызове getAdapter (для доступа к базовым объектам). Это привело к тому, что счетчики стали другими из-за дополнительного элемента «Загрузить еще» и выданного исключения.

Я сделал это только потому, что из документов видно, что это нормально

ListView.getAdapter Javadoc

Возвращает адаптер, используемый в данный момент в этом ListView. Возвращенный адаптер может быть не тем адаптером, который передан в setAdapter (ListAdapter), но может быть WrapperListAdapter.

aaronvargas
источник
У меня похожая проблема. Подскажите, пожалуйста, как это исправить.
May13ank
Если вы посмотрите на 2 примера кода выше, вы увидите mAdapter в ListView и getAdapter () в родительском элементе ListView (AdapterView). Если вы переопределите getAdapter (), результатом getAdapter () может быть не mAdapter. Если количество двух адаптеров отличается, вы получите ошибку.
Ааронваргас
3

Моя проблема была связана с использованием фильтра вместе с ListView.

При настройке или обновлении базовой модели данных ListView я делал что-то вроде этого:

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

призвание filter() в последней строке вызовет (и должен) вызвать метод notifyDataSetChanged()в фильтре publishResults(). Иногда это может работать нормально, особенно в моем быстром Nexus 5. Но на самом деле, он скрывает ошибку, которую вы заметите на более медленных устройствах или в ресурсоемких условиях.

Проблема в том, что фильтрация выполняется асинхронно, и, следовательно, между концом filter() оператора и вызовом publishResults(), как в потоке пользовательского интерфейса, может выполняться какой-то другой код потока пользовательского интерфейса и изменять содержимое адаптера.

Фактическое исправление легко, просто позвоните notifyDataSetChanged()также, прежде чем запрашивать выполнение фильтрации:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
cprcrack
источник
3

У меня есть список объектов Feed. Он добавляется и усекается из потока без пользовательского интерфейса. Он отлично работает с адаптером ниже. В FeedAdapter.notifyDataSetChangedлюбом случае, я звоню в теме интерфейса, но чуть позже. Мне это нравится, потому что мои объекты Feed остаются в памяти в Local Service, даже когда пользовательский интерфейс не работает.

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;
    }
}
илья
источник
2

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

Это хорошо работает и выглядит очень затягивающе, но, к сожалению, его можно разбить, достаточно часто касаясь показанных пунктов списка.

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

Ларс К.
источник
2

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

Баладжи
источник
1

Я столкнулся с подобной проблемой, вот как я решил в моем случае. Я проверяю, есть ли 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

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

Gusthema
источник
1

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

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

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

andude
источник
1

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

sek13300
источник
1
Пожалуйста, уточните ваше решение. Я тоже в ситуации
выгляжу
1
adapter.notifyDataSetChanged()
нитин джозеф
источник
2
Хорошей практикой в ​​Stack Overflow является добавление объяснения того, почему ваше решение должно работать или лучше, чем существующие решения. Для получения дополнительной информации прочитайте, как ответить .
Сэмюэл Лью
0

Я также получаю точно такую ​​же ошибку и использую 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();метод. Теперь мое приложение никогда не падает.

CHarris
источник
0

Пожалуйста, попробуйте одно из этих решений:

  1. Иногда, если вы добавляете новый объект в список данных в потоке (или doInBackgroundметоде), эта ошибка возникает. Решение: создайте временный список и добавьте данные в этот список в потоке (или doInBackground), затем скопируйте все данные из временного списка в список адаптеров в потоке пользовательского интерфейса (или onPostExcute).

  2. Убедитесь, что все обновления пользовательского интерфейса вызываются в потоке пользовательского интерфейса.

Phuc Tran
источник
0

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

         adapter.notifyDataSetChanged();

в

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

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

Хоби Сгонф
источник
0

Как сказал @Mullins: «
Я добавил элементы и вызвал их notifyDataSetChanged()в ветке пользовательского интерфейса, и я решил это. - Маллинс».

В моем случае у меня есть , asynctaskи я позвонил notifyDataSetChanged()в doInBackground()методе , и проблема будет решена, когда я звонил из onPostExecute()я получил исключение.

Сиро Майн
источник
0

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

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
Pascalius
источник
0

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

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

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

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
Сэм
источник
0

У меня такая же проблема. наконец я получил решение

перед обновлением списка, если программная клавиатура присутствует, сначала закройте ее. после этого установите источник данных и вызовите notifydatasetchanged ().

при внутреннем закрытии клавиатуры listview обновит свой интерфейс. он продолжает звонить до закрытия клавиатуры. тогда, если источник данных изменится, он выдаст это исключение. если данные обновляются в 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);
sreejith
источник
0

Мое решение:

1) создать temp ArrayList .

2) выполнять тяжелую работу (выборка строк sqlite, ...) в doInBackground Выполните методе и добавьте элементы в временный массив.

3) добавить все элементы из временного архива в onPostExecuteметод списка рассылки .

note:Вы можете удалить некоторые элементы из списка, а также удалить из базы данных sqlite и, возможно, удалить некоторые файлы, связанные с элементами из sdcard, просто удалить элементы из базы данных и удалить связанные с ними файлы и добавить их во временный массив в background thread. затем вUI thread удалить элементы, существующие в временном архиве из списка рассылки.

Надеюсь это поможет.

Nobody8
источник