Android, ListView IllegalStateException: “El contenido del adaptador ha cambiado pero ListView no recibió una notificación”

Lo que quiero hacer : ejecutar un hilo de fondo que calcula los contenidos de ListView y actualiza parcialmente ListView, mientras se calculan los resultados.

Lo que sé que debo evitar : no puedo meterme con el contenido de ListAdapter del hilo de fondo, así que heredé AsyncTask y publico el resultado (agregue entradas al adaptador) desde onProgressUpdate. Mi adaptador usa ArrayList de objetos de resultados, todas las operaciones en esas listas de arreglos están sincronizadas.

Investigación de otras personas : aquí hay datos muy valiosos. También sufrí lockings casi diarios para un grupo de ~ 500 usuarios, y cuando agregué el list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) en ongressUpdate, los lockings se redujeron en un factor de 10 pero no desaparecieron. (se sugirió en respuesta )

Lo que recibí a veces : tenga en cuenta que sucede muy raramente (una vez por semana para uno de los 3.5k usuarios). Pero me gustaría deshacerme de este error por completo. Aquí hay stacktrace parcial:

 `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) [...] 

¿Ayuda? Ya no es necesario, mira a continuación

RESPUESTA FINAL: Resultó que estaba llamando a notifyDataSetChanged cada 5 inserciones para evitar parpadeos y cambios repentinos en la lista. No se puede hacer de esa manera, siempre notifique el adaptador cuando la lista base cambie. Este error ya pasó para mí ahora.

Tuve el mismo problema.

Estaba agregando elementos a mi ArrayList fuera del hilo de UI.

Solución: He hecho ambas cosas, notifyDataSetChanged() adding the items y llamé notifyDataSetChanged() en el hilo de la interfaz de usuario.

Tuve el mismo problema, pero lo solucioné usando el método

 requestLayout(); 

de la clase ListView

Este es un problema de MultiThreading y el uso de bloques correctamente sincronizados Esto se puede evitar. Sin poner cosas adicionales en UI Thread y causando la pérdida de capacidad de respuesta de la aplicación.

También me enfrenté a lo mismo. Y como la respuesta más aceptada sugiere hacer cambios en los datos del adaptador desde UI Thread puede resolver el problema. Eso funcionará, pero es una solución rápida y fácil, pero no la mejor.

Como puedes ver en un caso normal. Actualiza el adaptador de datos del hilo de fondo y llama a notifyDataSetChanged en el hilo de la interfaz de usuario funciona.

Esta illegalStateException surge cuando un hilo de la interfaz de usuario está actualizando la vista y otra cadena de fondo cambia los datos de nuevo. Ese momento causa este problema.

Por lo tanto, si sincroniza todo el código que está cambiando los datos del adaptador y realiza una llamada de notificación a cambio de llamada. Este problema debería desaparecer. Como me fui y todavía estoy actualizando los datos del hilo de fondo.

Aquí está el código específico de mi caso para que otros se refieran.

Mi cargador en la pantalla principal carga los contactos de la guía telefónica en mis fonts de datos en segundo plano.

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

Este PhoneBookManager.getPhoneBookContacts lee el contacto de la agenda y los completa en los hashmaps. Que es directamente utilizable para los adaptadores de lista para dibujar la lista.

Hay un botón en mi pantalla. Eso abre una actividad donde se enumeran estos números de teléfono. Si configuro Adapter directamente sobre la lista antes de que termine el hilo anterior, su trabajo, que es un caso de navegación rápida, ocurre con menos frecuencia. Aparece la excepción. Cuál es el título de esta pregunta SO. Entonces tengo que hacer algo como esto en la segunda actividad.

Mi cargador en la segunda actividad espera a que se complete el primer hilo. Hasta que se muestre una barra de progreso. Verifique el loadInBackground de ambos cargadores.

Luego crea el adaptador y lo entrega a la actividad donde en el hilo ui llamo setAdapter.

Eso resolvió mi problema.

Este código es solo un fragmento. Debes cambiarlo para comstackrlo bien.

 @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()); } } } 

Espero que esto ayude

Lo resolví por tener 2 listas. Una lista que uso solo para el adaptador, y hago todos los cambios de datos / actualizaciones en la otra lista. Esto me permite hacer actualizaciones en una lista en una cadena de fondo, y luego actualizar la lista de “adaptadores” en el hilo principal / IU:

 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(); } }); } 

Escribí este código y lo ejecuté en una imagen de emulador 2.1 durante ~ 12 horas y no obtuve la IllegalStateException. Voy a darle al marco de Android el beneficio de la duda sobre este y digo que es muy probable que sea un error en tu código. Espero que esto ayude. Tal vez pueda adaptarlo a su lista y datos.

 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); } } 

Mi problema estaba relacionado con el uso de un filtro junto con ListView.

Al configurar o actualizar el modelo de datos subyacente de ListView, estaba haciendo algo como esto:

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

El filter() llamadas filter() en la última línea (y debe) provocar que se llame a publishResults() método publishResults() del filtro. Esto puede funcionar bien a veces, especialmente en mi rápido Nexus 5. Pero en realidad, está ocultando un error que notará con dispositivos más lentos o en condiciones de uso intensivo de recursos.

El problema es que el filtrado se realiza de forma asincrónica y, por lo tanto, entre el final de la sentencia filter() y la invocación de publishResults() , ambos en el subproceso UI, puede ejecutar otro código de subproceso de UI y cambiar el contenido del adaptador.

La solución real es fácil, simplemente llame a notifyDataSetChanged() también antes de solicitar que se realice el filtrado:

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

Tengo una lista si se alimentan objetos. Se adjunta y se trunca de un subproceso sin interfaz de usuario. Funciona bien con adaptador a continuación. Yo llamo a FeedAdapter.notifyDataSetChanged en el hilo de UI de todos modos, pero un poco más tarde. Me gusta esto porque mis objetos Feed permanecen en la memoria en el Servicio Local incluso cuando la IU está muerta.

 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; } } 

Estoy enfrentando el mismo problema con exactamente el mismo registro de errores. En mi caso onProgress() de AsyncTask agrega los valores al adaptador usando mAdapter.add(newEntry) . Para evitar que la IU se vuelva menos sensible, establezco mAdapter.setNotifyOnChange(false) y llamo a mAdapter.notifyDataSetChanged() 4 veces el segundo. Una vez por segundo, la matriz está ordenada.

Esto funciona bien y se ve muy adictivo, pero desafortunadamente es posible bloquearlo al tocar los elementos de la lista que se muestran con la suficiente frecuencia.

Pero parece que he encontrado una solución aceptable. Supongo que incluso si solo trabajas en el subproceso ui, el adaptador no acepta muchos cambios en sus datos sin llamar a notifyDataSetChanged() , debido a esto creé una cola que almacena todos los elementos nuevos hasta que los notifyDataSetChanged() mencionados hayan terminado. Si se alcanza este momento, agrego todos los elementos almacenados en una sola toma y llamo notifyDataSetChanged() . Hasta ahora no he podido bloquear la lista más .

Si esto hubiera sucedido intermitentemente, resulta que solo tuve este problema cuando la lista se desplazó después de que se hizo clic en el último elemento de “cargar más”. Si la lista no se desplazó, todo funcionó bien.

Después de MUCHA depuración, fue un error de mi parte, pero una incoherencia en el código de Android también.

Cuando ocurre la validación, este código se ejecuta en 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 " 

Pero cuando ocurre onChange, este código se dispara en AdapterView (principal de ListView)

  @Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); 

¡Observe cómo NO se garantiza que el adaptador sea el mismo!

En mi caso, dado que era un ‘LoadMoreAdapter’, devolvía el WrappedAdapter en la llamada getAdapter (para acceder a los objetos subyacentes). Esto dio lugar a que los recuentos fueran diferentes debido al elemento adicional “Cargar más” y a la excepción que se lanza.

Solo hice esto porque los documentos hacen que parezca que está bien hacer

ListView.getAdapter javadoc

Devuelve el adaptador actualmente en uso en este ListView. El adaptador devuelto puede no ser el mismo adaptador pasado a setAdapter (ListAdapter) pero podría ser un WrapperListAdapter.

Este es un error conocido en Android 4 a 4.4 (KitKat) y se resuelve en “> 4.4”

Vea aquí: https://code.google.com/p/android/issues/detail?id=71936

Incluso yo enfrenté el mismo problema en mi aplicación de notificación XMPP, el mensaje de los receptores debe agregarse a la vista de lista (implementado con ArrayList ). Cuando traté de agregar el contenido del receptor a través de MessageListener (hilo separado), la aplicación se cierra con el error anterior. Lo resolví agregando el contenido a mi arraylist & setListviewadapater través del método runOnUiThread , que es parte de la clase Activity. Esto resolvió mi problema.

Hace varios días me encontré con el mismo problema y causa varios miles de accidentes por día, alrededor del 0.1% de los usuarios cumplen con esta situación. setVisibility(GONE/VISIBLE) y requestLayout() , pero el recuento de requestLayout() solo disminuye un poco.

Y finalmente lo resolví. Nada con setVisibility(GONE/VISIBLE) . Nada con requestLayout() .

Finalmente encontré la razón por la que utilicé un notifyDataSetChanged() para llamar a notifyDataSetChanged() después de los datos de actualización, lo que puede llevar a una especie de:

  1. Actualiza datos a un objeto modelo (lo llamo un DataSource)
  2. El usuario toca listview (que puede llamar a checkForTap() / onTouchEvent() y finalmente llama a layoutChildren() )
  3. El adaptador obtiene datos del objeto modelo y llama a notifyDataSetChanged() y actualiza las vistas

Y cometí otro error: en getCount() , getItem() y getView() , uso campos directamente en DataSource, en lugar de copiarlos al adaptador. Así que finalmente se bloquea cuando:

  1. El adaptador actualiza los datos cuya última respuesta da
  2. Cuando vuelva la siguiente respuesta, DataSource actualiza los datos, lo que provoca un cambio en el recuento de elementos
  3. El usuario toca la vista de lista, que puede ser un toque o un movimiento o giro
  4. getCount() y getView() , y listview encuentra que los datos no son consistentes, y arroja excepciones como java.lang.IllegalStateException: The content of the adapter has changed but... Otra excepción común es IndexOutOfBoundException si usa encabezado / pie de página en ListView .

Así que la solución es fácil, solo copio datos al adaptador de mi DataSource cuando mi controlador activa el adaptador para obtener datos y llama a notifyDataSetChanged() . El accidente ahora nunca vuelve a suceder.

Tuve el mismo problema y lo resolví. Mi problema era que estaba usando una vista de lista, con un adaptador de matriz y con filtro. En el método performFiltering me estaba metiendo con la matriz que tiene los datos y era el problema, ya que este método no se ejecuta en el subproceso de la interfaz de usuario y, EVENTUALMENTE, plantea algunos problemas.

Una de las causas de este locking es que el objeto ArrayList no puede cambiar completamente. Entonces, cuando elimine un artículo, debo hacer esto:

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

Esto solucionó el problema por mí.

En mi caso, llamé al método GetFilter() en un adaptador del método TextWatcher() en la Actividad principal, y agregué los datos con un bucle For en GetFilter() . La solución fue cambiar el bucle For al método secundario AfterTextChanged() en la actividad principal y eliminar la llamada a GetFilter()

También estaba obteniendo exactamente el mismo error y usando 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 

Lo resolví poniendo adapter.notifyDataSetChanged(); en la parte inferior de mi hilo de UI, ese es mi método AsyncTask onPostExecute. Me gusta esto :

  protected void onPostExecute(Void aVoid) { all my other stuff etc... all my other stuff etc... adapter.notifyDataSetChanged(); } }); } 

Ahora mi aplicación funciona

EDITAR: De hecho, mi aplicación todavía se bloqueaba cada 1 de cada 10 veces, dando el mismo error.

Finalmente me encontré con runOnUiThread en una publicación anterior, que pensé que podría ser útil. Así que lo puse en mi método doInBackground, así:

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

Y adapter.notifyDataSetChanged(); el adapter.notifyDataSetChanged(); método. Ahora, mi aplicación nunca falla.

Enfrenté un problema similar, así es como lo resolví en mi caso. Verifico si la task ya está RUNNING o FINISHED porque una tarea puede ejecutarse solo una vez. A continuación, verá un código parcial y adaptado de mi solución.

 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); } } } 

Pruebe una de estas soluciones:

  1. A veces, si agrega un objeto nuevo a la lista de datos en un hilo (o método doInBackground ), se producirá este error. La solución es: crear una lista temporal y agregar datos a esta lista en threads (o doInBackground ), luego copiar todos los datos de la lista temporal a la lista de adaptadores en la interfaz de usuario thread (o onPostExcute )

  2. Asegúrese de que todas las actualizaciones de UI se invoquen en el hilo de UI.

tuve el mismo problema cuando agregue nuevos datos en el cargador de imágenes perezosas acabo de poner

  adapter.notifyDataSetChanged(); 

en

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

espero que te ayude

Como dijo @Mullins ”
Ambos agregué los artículos y llamé notifyDataSetChanged() en el hilo de la interfaz de usuario y resolví esto. – Mullins “.

En mi caso tengo asynctask y llamé a notifyDataSetChanged() en el método doInBackground() y el problema está resuelto, cuando llamé desde onPostExecute() recibí la excepción.

Tenía un ListAdapter personalizado y estaba llamando a super.notifyDataSetChanged() al principio y no al final del método

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

Tuve la misma situación, tuve muchos buttongroup insite mi elemento en listview y estaba cambiando algunos valores booleanos dentro de mi elemento como holder.rbVar.setOnclik …

mi problema ocurrió porque estaba llamando a un método dentro de getView (); y estaba guardando un objeto dentro de sharepreference, así que tuve el mismo error arriba

Cómo lo resolví; Eliminé mi método dentro de getView () para notifyDataSetInvalidated () y desapareció el problema

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

Yo tuve el mismo problema. finalmente recibí la solución

antes de actualizar listview, si el teclado está presente, ciérrelo primero. después de eso configure la fuente de datos y llame notifydatasetchanged ().

al cerrar el teclado internamente, listview actualizará su interfaz de usuario. sigue llamando hasta cerrar el teclado. esa vez si la fuente de datos cambia, lanzará esta excepción. si los datos se actualizan en onActivityResult, existe la posibilidad de que ocurra el mismo error.

  InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(v.getWindowToken(), 0); view.postDelayed(new Runnable() { @Override public void run() { refreshList(); } },100L); 

Mi solución:

1) crea una temp ArrayList .

2) haga sus trabajos pesados ​​(búsqueda de fila sqlite, …) en el método doInBackground y agregue elementos a la lista de arrays temporal.

3) agregue todos los elementos de la lista temporal a la lista de arrays de su listview en el método onPostExecute .

note: es posible que desee eliminar algunos elementos de listview y también eliminar de la base de datos sqlite y eliminar algunos archivos relacionados con los elementos de sdcard, simplemente elimine los elementos de la base de datos y elimine los archivos relacionados y agréguelos a la lista de arrays temporal en el background thread . luego, en el UI thread elimine los elementos existentes en el arraylist temporal de la lista de arrays de listview.

Espero que esto ayude.

    Intereting Posts