Android, ListView IllegalStateException: «Содержимое адаптера изменилось, но ListView не получил уведомление»
Что я хочу сделать : запустите фоновый stream, который вычисляет содержимое ListView и частично обновляет ListView, а результаты вычисляются.
Я знаю, что должен избегать : я не могу связать содержимое ListAdapter из фонового streamа, поэтому я унаследовал AsyncTask и опубликовал результат (добавить записи в адаптер) из onProgressUpdate. Мой адаптер использует ArrayList для объектов результатов, все операции с этими массивами синхронизированы.
Исследование других людей : здесь есть очень ценные данные. Я также страдал от почти ежедневных сбоев для группы из ~ 500 пользователей, и когда я добавил list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
в onProgressUpdate, сбой снизился в 10 раз, но не исчез. (это было предложено в ответ )
- Android L (API 21) - java.lang.IllegalArgumentException: служебное намерение должно быть явным
- Как добавить параметры в android http POST?
- Воспроизведение RTSP в приложении для Android
- Что делает postInvalidate ()?
- Расположение координатора с панелью инструментов во fragmentах или действиях
Иногда я получаю : обратите внимание, это происходит очень редко (раз в неделю для одного из пользователей 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 вставок, чтобы избежать мерцания и внезапных изменений списка. Это невозможно сделать таким образом, всегда уведомлять адаптер при изменении базового списка. Эта ошибка для меня уже давно исчезла.
- Apk должен быть подписан с теми же сертификатами, что и предыдущая версия
- Переключение между изображениями Android Navigation Drawer и Up caret при использовании fragmentов
- Как создать тему в уведомлениях FCM
- Как заставить Share Intent открыть конкретное приложение?
- андроидный захват видео кадра
- Стандартный переход fragmentа не оживляющий
- Как анимировать маркер в android-карте api V2?
- Обнаружение, если был получен ответ на исходящий вызов
Я была такая же проблема.
Я добавлял элементы в свой 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()
после данных обновления, что может привести к:
- Обновляет данные объекту модели (я называю это DataSource)
- Пользователь затрагивает listview (который может вызывать
checkForTap()
/onTouchEvent()
и, наконец, вызываетlayoutChildren()
) - Адаптер получает данные из объекта модели и вызывает
notifyDataSetChanged()
и обновляет представления
И я сделал еще одну ошибку: в getCount()
, getItem()
и getView()
я напрямую использую поля в DataSource, а не getView()
их в адаптер. Поэтому, наконец, он падает, когда:
- Адаптер обновляет данные, которые последний ответ дает
- Когда следующий ответ возвращается, DataSource обновляет данные, что вызывает изменение количества элементов
- Пользователь затрагивает listview, который может быть нажатием или перемещением или переворотом
-
getCount()
иgetView()
вызывается, а данные listviewgetView()
и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); } } }
Попробуйте одно из следующих решений:
-
Иногда, если вы добавляете новый объект в список данных в streamе (или
doInBackground
), эта ошибка возникает. Решением является создание временного списка и добавление данных в этот список в streamе (илиdoInBackground
), а затем копирование всех данных из временного списка в список адаптеров в streamе пользовательского интерфейса (илиonPostExcute
) -
Убедитесь, что все обновления пользовательского интерфейса вызывают в 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.
Надеюсь это поможет.