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

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

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

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

То, что я иногда получаю : обратите внимание, это происходит очень редко (раз в неделю для одного из пользователей 3,5 тыс.). Но я бы полностью избавился от этой ошибки. Вот частичная 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) [...] 

Помогите? Больше не нужно, см. Ниже

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

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

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

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

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

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

 requestLayout(); 

Из класса ListView

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

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

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

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

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

Вот мой конкретный код для других людей.

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

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

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

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

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

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

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

Я решил это, имея 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(); } }); } 

Я написал этот код и запустил его в образ эмулятора 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); } } 

Моя проблема связана с использованием фильтра вместе с 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); } 

У меня есть список, если объекты Feed. Он добавлен и усечен из нити-нити. Он отлично работает с адаптером ниже. В любом случае я вызываю FeedAdapter.notifyDataSetChanged в потоке пользовательского интерфейса, но немного позже. Мне это нравится, потому что мои объекты 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; } } 

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

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

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

Это известная ошибка в Android 4 до 4.4 (KitKat) и разрешена в "> 4.4"

См. Здесь: https://code.google.com/p/android/issues/detail?id=71936

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

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

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

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

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

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

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

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

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

После отладки MUCH это была ошибка с моей стороны, но также была несогласованность в коде 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 (для доступа к базовым объектам). Это привело к тому, что подсчеты отличались из-за дополнительного «Load More» и исключения.

Я только сделал это, потому что документы делают это похоже, что это нормально

ListView.getAdapter javadoc

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

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

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

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

Это фиксировало крушение для меня.

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

Я также получал точно такую ​​же ошибку и использовал 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(); } }); } 

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

EDIT: На самом деле мое приложение все еще разбилось примерно раз в 1 раз в 10 раз, давая ту же ошибку.

В конце концов я столкнулся с runOnUiThread на предыдущем посту, который, как я думал, может быть полезен. Поэтому я поместил его в свой метод doInBackground, например:

 @Override protected Void doInBackground(Void... voids) { runOnUiThread(new Runnable() { public void run() { etc... etc... 

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

Я столкнулся с подобной проблемой, вот как я решил в моем случае. Я проверяю, является ли 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. Иногда, если вы добавляете новый объект в список данных в потоке (или doInBackground ), эта ошибка возникает. Решением является создание временного списка и добавление данных в этот список в потоке (или doInBackground ), а затем копирование всех данных из временного списка в список адаптеров в потоке пользовательского интерфейса (или onPostExcute )

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

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

  adapter.notifyDataSetChanged(); 

в

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

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

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

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

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

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

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

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

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

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