Un montón de recolección de basura en un listview

Tengo un ListView que utiliza un adaptador de encargo. El getView del adaptador personalizado utiliza todas las prácticas recomendadas:

@Override public View getView(int position, View convertView, ViewGroup parent) { SuscriptionsViewsHolder holder; ItemInRootList item = mItemsInList.get(position); if (convertView == null) { convertView = mInflater.inflate(R.layout.label, null); holder = new SuscriptionsViewsHolder(); holder.label = (TextView) convertView.findViewById(R.id.label_label); holder.icon = (ImageView) convertView.findViewById(R.id.label_icon); convertView.setTag(holder); } else { holder = (SuscriptionsViewsHolder) convertView.getTag(); } String text = String.format("%1$s (%2$s)", item.title, item.unreadCount); holder.label.setText(text); holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file ); return convertView; } 

Sin embargo, cuando me desplazo, es lento debido a la recolección de basura pesada:

 GC_EXTERNAL_ALLOC freed 87K, 48% free 2873K/5447K, external 516K/519K, paused 30ms GC_EXTERNAL_ALLOC freed 7K, 48% free 2866K/5447K, external 1056K/1208K, paused 29ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2866K/5447K, external 1416K/1568K, paused 28ms GC_EXTERNAL_ALLOC freed 5K, 48% free 2865K/5447K, external 1600K/1748K, paused 27ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2865K/5447K, external 1780K/1932K, paused 30ms GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms GC_EXTERNAL_ALLOC freed 3K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 28ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 26ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 27ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 34ms 

¿Qué parece estar mal?

EDIT @ 12: 47 GMT:

De hecho, es un poco más complicado que esto. Mi aplicación de IU se basa en 2 partes. Uno es el cerebro de una pantalla, la creación de las vistas, la manipulación de entrada de usuario, etc El otro es un Fragment si el dispositivo tiene Android 3.0, de lo contrario es una Activity .

El GC ocurrió en mi dispositivo Nexus One 2.3.3, por lo que el uso de la Activity . No tengo mi Xoom conmigo para probar el comportamiento con un Fragment .

Podría publicar la fuente si es necesario, pero permítanme tratar de explicarlo:

  • RootList es el cerebro de la interfaz de usuario. Contiene :
    • Una List<> de elementos que se colocarán en el ListView .
    • Un método que genera esta lista desde un SQLite db
    • Un BaseAdapter personalizado que contiene básicamente sólo el método getView pegado anteriormente
  • RootListActivity es una ListActivity , que:
    • Utiliza un diseño XML
    • La disposición tiene por supuesto un listview con id android.id.list
    • Activity devoluciones de llamada de la Activity se reenvían a la clase RootList utilizando una instancia de RootList creada cuando se crea la actividad (constructor, no onCreate )
    • En el onCreate , llamo a los métodos de RootList que crearán la lista de elementos y estableceré los datos de la lista a una nueva instancia de mi clase personalizada derivada de BaseAdapter

EDIT el 17 de mayo @ 9:36 PM GMT:

Aquí está el código de la actividad y la clase que hace las cosas. Http://pastebin.com/EgHKRr4r

Encontré el problema. Mi diseño XML para la actividad fue:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <include android:id="@+id/rootlist_header" layout="@layout/pre_honeycomb_action_bar" /> <ListView android:id="@android:id/list" android:layout_below="@id/rootlist_header" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_weight="1" android:textColor="#444444" android:divider="@drawable/list_divider" android:dividerHeight="1px" android:cacheColorHint="#00000000" /> </RelativeLayout> 

Si quito el android:cacheColorHint="#00000000" , el GC pesado está hacia fuera, y el desplazamiento es liso ! 🙂

Realmente no sé por qué este parámetro se estableció, porque no lo necesito. Tal vez copypasted demasiado en lugar de realmente construir mi diseño XML.

GRACIAS por su apoyo, realmente aprecio su ayuda.

También tuve un problema con el android:cacheColorHint="#00000000" . Necesitaba usarlo aunque porque tengo una imagen de fondo fija.

Me pareció que la configuración de las siguientes propiedades deshabilita el almacenamiento en caché de vista de lista de android y la lista se desplaza sin problemas (el GC no se llama a menudo):

 android:scrollingCache="false" android:animationCache="false" 

También si u desea deshacerse de la selección de color por defecto use esto:

 android:listSelector="#00000000" 

Debe utilizar DDMS y su localizador de asignación para averiguar exactamente qué está generando tantos objetos. Tenga en cuenta, sin embargo, que String.format () es bien conocido por generar grandes cantidades de basura.

http://developer.android.com/resources/articles/track-mem.html

En realidad, pasé por su código y hackeado un poco para que funcione en mis dispositivos. Puedo confirmar que su ListAdapter está bien, y que el problema está en otra parte.

Esto es lo que usted hace en el método createDefaultLabelsList ().

 mItemsInList.clear(); ItemInRootList item; do { item = new ItemInRootList(); item.isLabel = true; item.id = c.getString(c.getColumnIndex(Labels.KEY_ID)); item.title = Labels.label(item.id); //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL)); item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT)); mItemsInList.add(item); } while (c.moveToNext()); 

Parece que no usar una variable local en su bucle es la causa de su problema de rendimiento, lo creas o no. Sustitúyalo por:

 mItemsInList.clear(); do { ItemInRootList item = new ItemInRootList(); item.isLabel = true; item.id = c.getString(c.getColumnIndex(Labels.KEY_ID)); item.title = Labels.label(item.id); //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL)); item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT)); mItemsInList.add(item); } while (c.moveToNext()); 

Y disfrutar de su lista ardiente rápido! Esto lo resolví para mí en htc héroe (2.1) y Xoom (3.1) usando una ListActivity.

Así es como lo habría hecho en primer lugar, pero no estoy exactamente seguro de cómo esto explica el comportamiento lento con su implementación original.

Encontré esto porque para reproducir su problema tuve que reemplazar la forma en que construyó la lista, acaba de crear una lista de etiquetas con 10k al azar y lo vio desplazarse muy rápido. Entonces noté que no usamos exactamente el mismo bucle, usted estaba agregando la misma referencia una y otra vez mientras yo estaba agregando 10k referencias diferentes. Cambié a su implementación y reproducido el error.

esta

  String text = String.format("%1$s (%2$s)", item.title, item.unreadCount); 

Hará que el GC se llame muchas veces.

String.format() asigna algo de memoria temporal además de la cadena que finalmente produce.

Otra manera de hacer esto.

Utilizando la clase StringBuilder , así.

 StringBuilder builder = new StringBuilder(128); @Override public View getView(int position, View convertView, ViewGroup parent) { SuscriptionsViewsHolder holder; ItemInRootList item = mItemsInList.get(position); if (convertView == null) { convertView = mInflater.inflate(R.layout.label, null); holder = new SuscriptionsViewsHolder(); holder.label = (TextView) convertView.findViewById(R.id.label_label); holder.icon = (ImageView) convertView.findViewById(R.id.label_icon); convertView.setTag(holder); } else { holder = (SuscriptionsViewsHolder) convertView.getTag(); } 

String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);

  builder.setLength(0); builder.append(item.title).append(" (").append(item.unreadCount).append(")"); holder.label.setText(builder.toString()); holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file ); return convertView; } 

String text = String.format ("% 1 $ s (% 2 $ s)", item.title, item.unreadCount); Puede ser String.format () crea una gran cantidad de cadenas intermedias que contamina el GC. Intente StringBuilder e intente almacenar en caché la cadena que construye. También como @tmho dijo tratar holder.icon.setImageDrawable (); En lugar de holder.icon.setImageResource (); Porque setImageResource () crea objeto Drawable cada vez. Y cada Drawable es aproximadamente 50-60 bytes. Parece que el mapa de bits no está duplicado en este caso, sólo contenedor de dibujo, pero puede ser suficiente para causar llamadas periódicas GC.

Intente holder.icon.setImageBitmap(); En lugar de holder.icon.setImageResource();

Esto comienza con 4.4.4 Actualización de KitKat. Vuelva a ejecutar código ya código normal en él y este problema apareció. ListView es completamente inutilizable ahora debido a scroll muy lento. (Nexus 4)

Y no estoy usando ningún formato en él.

Cuando se está desplazando la vista, aparece un mensaje de GC.

Tengo una pregunta similar, mi lista mostrará imágenes de internet, y guardarlos en la tarjeta SD. Cuando el lanzamiento de la aplicación más adelante, la imagen local será utilizada, entonces cuando enrollo el listview, las porciones de GC aparecen en el logcat, y la visión atontará levemente.

Me di cuenta de que cuando la aplicación se puso en marcha por primera vez, con imágenes de Internet, el listview muestra normalmente.

Por cierto, he utilizado el android-imagedownloader de http://code.google.com/p/android-imagedownloader/ , y añadir algún código de caché de archivos locales.

Así que aquí está mi solución, cuando se carga la imagen de un archivo local, voy a agregar el objeto de mapa de bits a la memoria caché (un HashMap estática), después de eso, cuando el listview desplazarse hacia arriba y hacia abajo, obtendrá la imagen de memoria caché, y lotes GC no volvió a suceder.

  • Android adecuada limpieza / eliminación
  • java.lang.OutOfMemoryError: Límite de sobrecarga de GC superado en Android 1.4
  • ¿Por qué la basura de Android recopila tantas veces con Jacksons ObjectMapper?
  • Android - Bitmap y gestión de la memoria?
  • ¿Puedo confiar en el recolector de basura para detener un AsyncTask?
  • Gran cantidad de memoria no recogida de basura
  • El servicio detenido está haciendo la recolección de basura constante
  • ¿Cómo inicia el Android Studio el Garbage Collector y cómo funciona?
  • SoftReference obtiene recolección de basura demasiado temprano
  • GC_FOR_ALLOC es más "serio" al investigar el uso de la memoria?
  • LeakCanary detecta pérdida de memoria de Android WebView
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.