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

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

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

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

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

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

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

 requestLayout(); 

из classа ListView

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

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

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

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

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

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

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

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

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

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

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

Затем он создает адаптер и доставляет его в действие, в котором на ii-streamе я вызываю setAdapter.

Это решило мою проблему.

Этот код является только fragmentом. Вы должны изменить его, чтобы скомпилировать для вас.

 @Override public Loader onCreateLoader(int arg0, Bundle arg1) { return new PhoneBookContactLoader(this); } @Override public void onLoadFinished(Loader arg0, PhoneBookContactAdapter arg1) { contactList.setAdapter(adapter = arg1); } /* * AsyncLoader to load phonebook and notify the list once done. */ private static class PhoneBookContactLoader extends AsyncTaskLoader { private PhoneBookContactAdapter adapter; public PhoneBookContactLoader(Context context) { super(context); } @Override public PhoneBookContactAdapter loadInBackground() { synchronized (SingleTonProvider.getInstance()) { return adapter = new PhoneBookContactAdapter(getContext()); } } } 

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

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

 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 adapter; ListView list; AsyncTask task; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1); this.list = this.getListView(); this.list.setAdapter(this.adapter); this.task = new AsyncTask() { 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 newContacts, String filter) { this.allContacts = newContacts; this.filteredContacts = newContacts; getFilter().filter(filter); } 

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

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

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

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

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

 public class FeedAdapter extends BaseAdapter { private int size = 0; private final List objects; public FeedAdapter(Activity context, List 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 раза второй. Один раз в секунду массив сортируется.

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

Но, похоже, я нашел приемлемое обходное решение. Я предполагаю, что даже если вы просто работаете над streamом ui, адаптер не принимает много изменений в своих данных без вызова notifyDataSetChanged() , из-за этого я создал очередь, в которой хранятся все новые элементы до тех пор, пока указанные 300 мс не будут завершены. Если этот момент достигнут, я добавляю все сохраненные элементы за один снимок и вызываю 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.

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

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

Даже я столкнулся с той же проблемой в своем приложении уведомления XMPP, сообщение приемников должно быть добавлено обратно в представление списка (реализовано с помощью ArrayList ). Когда я попытался добавить контент получателя через MessageListener (отдельный stream), приложение завершает работу с ошибкой выше. Я решил это, добавив контент в мой arraylist & setListviewadapater помощью метода runOnUiThread который является частью classа 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() . Крушение теперь больше никогда не повторится.

У меня была та же проблема, и я решил. Моя проблема заключалась в том, что я использовал listview , с адаптером массива и с фильтром. В методе performFiltering я возился с массивом, у которого есть данные, и это была проблема, поскольку этот метод не работает в streamе пользовательского интерфейса, и 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(); в нижней части моего streamа пользовательского интерфейса, это мой метод 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{ List Itens; @Override protected void onPreExecute() { //your code list.setVisibility(View.GONE); adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList()); 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. Иногда, если вы добавляете новый объект в список данных в streamе (или doInBackground ), эта ошибка возникает. Решением является создание временного списка и добавление данных в этот список в streamе (или doInBackground ), а затем копирование всех данных из временного списка в список адаптеров в streamе пользовательского интерфейса (или onPostExcute )

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

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

  adapter.notifyDataSetChanged(); 

в

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

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

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

В моем случае у меня есть 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(); } 

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

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

Мое решение:

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

2) сделайте свои тяжелые работы (sqlite row fetch, …) в doInBackground и добавьте элементы в temp arraylist.

3) добавьте все элементы из temp araylist в arraylist вашего listview в методе onPostExecute .

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

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

  • обновить fragment UI от fragmentа.
  • Использование Retrofit в Android
  • Действительно ли fragmentам нужен пустой конструктор?
  • Ошибка: (23, 17) Не удалось выполнить: junit: junit: 4.12
  • Как вы проверяете, играет ли музыка с помощью широковещательного приемника?
  • Как сделать HTTP-сообщение в Android?
  • Android Volley - BasicNetwork.performRequest: Неожиданный код ответа 400
  • Какова основная цель методов setTag () getTag () View?
  • Могу ли я прокручивать ScrollView программно в Android?
  • getSize () дает мне ошибки
  • Установите стиль TextView (жирный или курсив)
  • Давайте будем гением компьютера.