getposition - Android, ListView IllegalStateException: "El contenido del adaptador ha cambiado pero ListView no recibió una notificación"
custom arrayadapter kotlin (24)
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.
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í bloqueos casi diarios para un grupo de ~ 500 usuarios, y cuando agregué el list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
en ongressUpdate, los bloqueos se redujeron en un factor de 10 pero no desaparecieron. (se sugirió en answer )
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.
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()
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<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);
}
}
}
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<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);
}
}
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
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 fuentes 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 compilarlo bien.
@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());
}
}
}
Espero que esto ayude
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 .
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:
- Actualiza datos a un objeto modelo (lo llamo un DataSource)
- El usuario toca listview (que puede llamar a
checkForTap()
/onTouchEvent()
y finalmente llama alayoutChildren()
) - 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:
- El adaptador actualiza los datos cuya última respuesta da
- Cuando vuelva la siguiente respuesta, DataSource actualiza los datos, lo que provoca un cambio en el recuento de elementos
- El usuario toca la vista de lista, que puede ser un toque o un movimiento o giro
-
getCount()
ygetView()
, y listview encuentra que los datos no son consistentes, y arroja excepciones comojava.lang.IllegalStateException: The content of the adapter has changed but...
Otra excepción común esIndexOutOfBoundException
si usa encabezado / pie de página enListView
.
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.
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.
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();
}
});
}
Mi problema estaba relacionado con el uso de un Filter junto con ListView.
Al configurar o actualizar el modelo de datos subyacente de ListView, estaba haciendo algo como esto:
public void updateUnderlyingContacts(List<Contact> 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<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
notifyDataSetChanged(); // Fix
getFilter().filter(filter);
}
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.
Pruebe una de estas soluciones:
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 (odoInBackground
), luego copiar todos los datos de la lista temporal a la lista de adaptadores en la interfaz de usuario thread (oonPostExcute
)Asegúrese de que todas las actualizaciones de UI se invoquen en el hilo de UI.
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.
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.
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();
}
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<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;
}
}
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.
Tuve el mismo problema, pero lo solucioné usando el método
requestLayout();
de la clase ListView
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 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();
}
Una de las causas de este bloqueo 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í.
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);
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