Ответ 1
У меня была такая же проблема.
Я добавлял элементы к моему ArrayList
вне потока пользовательского интерфейса.
Решение: я сделал оба, adding the items
и вызвал notifyDataSetChanged()
в потоке пользовательского интерфейса.
Что я хочу сделать: запустите фоновый поток, который вычисляет содержимое ListView и частично обновляет ListView, в то время как результаты вычисляются.
Я знаю, что мне нужно избегать. Я не могу связать содержимое ListAdapter из фонового потока, поэтому я унаследовал AsyncTask и опубликовал результат (добавить записи в адаптер) из onProgressUpdate. Мой адаптер использует ArrayList для объектов результатов, все операции с этими arraylists синхронизированы.
Исследование других людей: здесь очень ценные данные здесь. Я также страдал от почти ежедневных сбоев для группы из ~ 500 пользователей, и когда я добавил блок list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
в onProgressUpdate, сбой снизился в 10 раз, но не исчез. (это было предложено в answer)
Что я иногда получаю: обратите внимание, это происходит очень редко (раз в неделю для одного из пользователей 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
вне потока пользовательского интерфейса.
Решение: я сделал оба, 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 читает контакт из телефонной книги и заполняет их в хэшмапах. Что непосредственно можно использовать для списков адаптеров для рисования списка.
На моем экране есть кнопка. Это открывает активность, в которой указаны эти номера телефонов. Если я непосредственно установилAdapter над списком до того, как предыдущий поток завершит свою работу, что происходит быстро, навигация происходит реже. Появляется исключение. Это название этого вопроса SO. Поэтому я должен сделать что-то подобное во втором действии.
Мой загрузчик во втором действии ждет завершения первого потока. Пока не появится индикатор выполнения. Проверьте loadInBackground обоих погрузчиков.
Затем он создает адаптер и доставляет его в действие, в котором в потоке ui я вызываю 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);
}
}
Если бы это происходило с перерывами, получается, что у меня была эта проблема, когда список был прокручен после того, как был нажат последний элемент "загрузить больше". Если список не прокручивался, все работало нормально.
После отладки 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.
Моя проблема связана с использованием Filter вместе с ListView.
При настройке или обновлении базовой модели данных ListView я делал что-то вроде этого:
public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
getFilter().filter(filter);
}
Вызов filter()
в последней строке будет (и должен) вызывать notifyDataSetChanged()
для вызова в методе Filter 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;
}
}
Несколько дней назад я столкнулся с той же проблемой и вызвал несколько тысяч сбоев в день, около 0,1% пользователей столкнулись с этой ситуацией. Я пробовал setVisibility(GONE/VISIBLE)
и requestLayout()
, но количество сбоев только немного уменьшилось.
И я, наконец, решил. Ничего с setVisibility(GONE/VISIBLE)
. Ничего с requestLayout()
.
Наконец, я нашел причину: я использовал Handler
для вызова notifyDataSetChanged()
после данных обновления, что может привести к следующему:
checkForTap()
/onTouchEvent()
и, наконец, вызывает layoutChildren()
)notifyDataSetChanged()
и обновляет представленияИ я сделал еще одну ошибку, что в getCount()
, getItem()
и getView()
я напрямую использую поля в DataSource, а не копирую их в адаптер. Таким образом, он заканчивается, когда:
getCount()
и getView()
вызывается, а данные listview несовместимы и вызывают исключения типа java.lang.IllegalStateException: The content of the adapter has changed but...
. Другим распространенным исключением является IndexOutOfBoundException
, если вы используете header/footer в ListView
.Таким образом, решение легко, я просто копирую данные в адаптер из своего DataSource, когда мой обработчик запускает адаптер для получения данных и звонков notifyDataSetChanged()
. Сбой теперь никогда не повторится.
Даже я столкнулся с той же проблемой в своем приложении уведомления XMPP, сообщение приемников должно быть добавлено обратно в представление списка (реализовано с помощью ArrayList
). Когда я попытался добавить содержимое приемника через MessageListener
(отдельный поток), приложение завершает работу с ошибкой выше. Я решил это, добавив контент в мой метод ArrayList
и setListviewadapater
через runOnUiThread
, который является частью класса Activity. Это решило мою проблему.
Я столкнулся с той же проблемой с точно таким же журналом ошибок.
В моем случае 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
У меня была та же проблема, и я решил это. Моя проблема заключалась в том, что я использовал listview
с адаптером массива и с фильтром. В методе performFiltering
я возился с массивом, у которого есть данные, и это была проблема, поскольку этот метод не работает в потоке пользовательского интерфейса, и EVENTUALLY вызывает некоторые проблемы.
Одной из причин этого сбоя является то, что объект ArrayList
не может полностью измениться.
Поэтому, когда я удаляю элемент, я должен это сделать:
mList.clear();
mList.addAll(newDataList);
Это фиксировало крушение для меня.
В моем случае я вызвал метод GetFilter()
на адаптере из метода TextWatcher()
в основной операции, и я добавил данные в цикл For на GetFilter()
.
Решением было изменение цикла For на AfterTextChanged()
sub method в основном Управлении и удаление вызова GetFilter()
У меня возникла аналогичная проблема, вот как я решил в моем случае. Я проверяю, есть ли 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);
}
}
}
Попробуйте одно из следующих решений:
Иногда, если вы добавляете новый объект в список данных в потоке (или doInBackground
метод), эта ошибка возникает. Решением является создание временного списка и добавление данных в этот список в потоке (или doInBackground
), а затем копирование всех данных из временного списка в список адаптеров в потоке пользовательского интерфейса (или onPostExcute
)
Убедитесь, что все обновления пользовательского интерфейса вызывают в потоке пользовательского интерфейса.
У меня была такая же проблема при добавлении новых данных в ленивый загрузчик изображений я просто положил
adapter.notifyDataSetChanged();
в
protected void onPostExecute(Void args) {
adapter.notifyDataSetChanged();
// Close the progressdialog
mProgressDialog.dismiss();
}
надеюсь, что это поможет вам
Как @Mullins сказал: "
Я добавил элементы и назвал notifyDataSetChanged()
в потоке пользовательского интерфейса, и я решил это. - Mullins ".
В моем случае у меня есть asynctask
, и я вызвал notifyDataSetChanged()
в методе doInBackground()
, и проблема решена, когда я вызвал из onPostExecute()
, я получил исключение.
У меня был пользовательский ListAdapter
и вызывал super.notifyDataSetChanged()
в начале, а не в конце метода
@Override
public void notifyDataSetChanged() {
recalculate();
super.notifyDataSetChanged();
}
Я также получал точно такую же ошибку и использовал 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();
. Теперь мое приложение никогда не падает.
У меня было то же самое, у меня было много элементов buttongroup, которые содержали мой элемент в listview, и я менял некоторые логические значения внутри моего элемента, такие как holder.rbVar.setOnclik...
моя проблема возникла из-за того, что я вызывал метод внутри getView(); и сохранял объект внутри sharepreference, поэтому у меня была такая же ошибка выше
Как я решил это; Я удалил свой метод внутри getView(), чтобы notifyDataSetInvalidated() и проблема исчезла
@Override
public void notifyDataSetChanged() {
saveCurrentTalebeOnShare(currentTalebe);
super.notifyDataSetChanged();
}
У меня была та же проблема. наконец, я получил решение
перед обновлением списка, если мягкая клавиатура присутствует, сначала закройте его. после этого установите источник данных и вызовите notifydatasetchanged().
при закрытии внутренней панели клавиатуры будет обновлен ее ui. он продолжает звонить до закрытия клавиатуры. в этот раз, если источник данных изменит это, он будет выбрасывать это исключение. если данные обновляются в 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,...) в методе doInBackground
и добавьте элементы в временный массив.
3) добавить все элементы из временного архива в ваш список рассылки в методе onPostExecute
.
note:
Вы можете удалить некоторые элементы из списка, а также удалить из базы данных sqlite и, возможно, удалить некоторые файлы, связанные с элементами из sdcard, просто удалить элементы из базы данных и удалить связанные с ними файлы и добавить их во временный массив в background thread
. затем в UI thread
удалите элементы, существующие в временном массиве из списка рассылки.
Надеюсь это поможет.
adapter.notifyDataSetChanged()