Android: excepción de falta de memoria en la Galería

Mi aplicación muestra una lista de 9 categorías y cada categoría muestra un flujo de cobertura basado en la Galería (gentilmente ofrecido por Neil Davies aquí ) con imágenes de la categoría seleccionada.
Las imágenes se extraen de la Web, cada una de ellas desde 300 K hasta 500 K de tamaño, y se almacenan en una lista de mallas de Dratables. Estos datos están vinculados al flujo de cobertura utilizando un BaseAdapter (código a continuación).
Cada vez que salgo del flujo de cobertura y vuelvo a la lista de categorías, borro el arrayList (de nuevo, código a continuación).
En el escenario 1, mi arrayList contiene 5 Drawables. En este escenario, puedo navegar libremente por todas las categorías y mostrar sus imágenes. Durante mi prueba, hice un ciclo de 5 categorías en todas las categorías, lo que parece suficiente para determinar que no hay problema.
En el escenario 2, mi arrayList contiene 10 dibujables. En este escenario, recibo una excepción OutOfMemoryError mientras reviso imágenes dentro de la 5ta o 6ta categoría:

 07-13 08: 38: 21.266: ERROR / dalvikvm-heap (2133): asignación externa de 819840 bytes demasiado grande para este proceso.
 07-13 08: 38: 21.266: ERROR / (2133): VM no nos permitirá asignar 819840 bytes
 07-13 08: 38: 21.277: DEPURACIÓN / skia (2133): --- decodificador-> deencoding devuelto falso
 07-13 08: 38: 21.287: WARN / dalvikvm (2133): threadid = 25: salida del hilo con excepción no detectada (group = 0x4001b188)
 07-13 08: 38: 21.296: ERROR / AndroidRuntime (2133): controlador no capturado: thread Thread-64 que sale debido a una excepción no detectada
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): java.lang.OutOfMemoryError: el tamaño del bitmap excede el presupuesto de VM
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): en android.graphics.BitmapFactory.nativeDecodeStream (método nativo)
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): en android.graphics.BitmapFactory.decodeStream (BitmapFactory.java:459)
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): en android.graphics.BitmapFactory.decodeResourceStream (BitmapFactory.java:323)
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): en android.graphics.drawable.Drawable.createFromResourceStream (Drawable.java:697)
 07-13 08: 38: 21.308: ERROR / AndroidRuntime (2133): en android.graphics.drawable.Drawable.createFromStream (Drawable.java:657)

Esto no tiene sentido para mí. Si estoy perdiendo memoria, habría esperado colapsar en algún momento en el escenario 1, pero revisé todas las categorías una cantidad sustancial de veces y no choqué. También utilicé el complemento de Memory Analyzer para Eclipse que no presentó ningún culpable potencial.
Si el sistema no pudiera manejar 10 imágenes, como en Scenarion 2, habría esperado chocar en la primera categoría, pero colapsé solo después de 5 o 6 categorías.
Cierto código:

Las funciones del adaptador Coverflow:

public int getCount() { return DataManager.getInstance().getImageBufferInstance().getImageArraySize(); } public Object getItem(int position) { return DataManager.getInstance().getImagesBuffer().get(position); } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { ImageView i; if (convertView == null) i = new ImageView(mContext); else i = (ImageView)convertView; Drawable bufferedImage = (Drawable)getItem(position); Log.v("getView", "position: " + position); i.setImageDrawable(bufferedImage); i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth() / 2, Utils.getInstance().getScreenHeight() / 2)); i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); try{ //Make sure we set anti-aliasing otherwise we get jaggies BitmapDrawable drawable = (BitmapDrawable) i.getDrawable(); drawable.setAntiAlias(true); } catch (Exception e) { Log.v("getView", "Exception: " + e.toString()); } return i; } 

poblando la fuente de datos al ingresar a la categoría:

 for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++) { String imageUrl = ImageBuffer.getInstance().getImageUrl(i); Log.v("Initial", imageUrl); Drawable fullImage = AsyncImageLoader.getInstance().loadImageByUrl(imageUrl); ImageBuffer.getInstance().getImages().add(i, fullImage); } 

borrar la fuente de datos al salir de la categoría (en finish ()):

 for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++) { if (ImageBuffer.getInstance().images.get(i) != null) { ImageBuffer.getInstance().images.get(i).setCallback(null); ImageBuffer.getInstance().images.set(i, null); } } 

EDITAR:

OK, apliqué la función LogHeap de Mathias en mi flujo de cobertura y aquí hay algunos resultados. Antes de cargar la primera galería:

 DEBUG / Aplicación (5221): depuración.  ==============================
 DEBUG / Aplicación (5221): debug.heap native: asignado 6.20MB de 6.28MB (0.07MB libre) en [com.example.Coverflow]
 DEBUG / Aplicación (5221): debug.memory: asignado: 4.00MB de 24.00MB (0.00MB gratis)
 DEBUG / dalvikvm (5221): GC liberó 4558 objetos / 638152 bytes en 84ms
 DEBUG / dalvikvm (5221): GC liberó 17 objetos / 808 bytes en 67ms

Después de ingresar a la primera galería:

 DEBUG / Aplicación (5221): depuración.  ==============================
 DEBUG / Aplicación (5221): debug.heap native: asignado 14.90MB de 16.89MB (0.07MB gratis) en [com.example.Coverflow]
 DEBUG / Aplicación (5221): debug.memory: asignado: 4.00MB de 24.00MB (1.00MB gratis)
 DEBUG / dalvikvm (5221): GC liberó 357 objetos / 50080 bytes en 68ms
 DEBUG / dalvikvm (5221): GC liberó 353 objetos / 27312 bytes en 67 ms

Después de existir la primera galería:

 DEBUG / Aplicación (5221): depuración.  ==============================
 DEBUG / Aplicación (5221): debug.heap native: asignado 14.83MB de 16.89MB (0.11MB libre) en [com.example.Coverflow]
 DEBUG / Aplicación (5221): debug.memory: asignado: 4.00MB de 24.00MB (1.00MB gratis)
 DEBUG / dalvikvm (5221): GC liberó 330 objetos / 17920 bytes en 77ms
 DEBUG / dalvikvm (5221): GC liberó 13 objetos / 760 bytes en 67 ms

Después de ingresar a la quinta galería:

 DEBUG / Aplicación (5221): depuración.  ==============================
 DEBUG / Aplicación (5221): debug.heap native: asignado 16.80MB de 23.32MB (0.08MB libre) en [com.example.Coverflow]
 DEBUG / Aplicación (5221): debug.memory: asignado: 4.00MB de 24.00MB (1.00MB gratis)
 DEBUG / dalvikvm (5221): GC liberó 842 objetos / 99256 bytes en 73ms
 DEBUG / dalvikvm (5221): GC liberó 306 objetos / 24896 bytes en 69ms

Después de salir de la quinta galería:

 DEBUG / Aplicación (5221): depuración.  ==============================
 DEBUG / Aplicación (5221): debug.heap native: asignado 16.74MB de 23.32MB (0.11MB gratis) en [com.example.Coverlow]
 DEBUG / Aplicación (5221): debug.memory: asignado: 4.00MB de 24.00MB (1.00MB gratis)
 DEBUG / dalvikvm (5221): GC liberó 331 objetos / 18184 bytes en 68ms
 DEBUG / dalvikvm (5221): GC liberó 60 objetos / 3128 bytes en 68ms

Parece que se asigna más y más memoria al ingresar a una galería, pero se libera muy poco después de salir. ¿No estoy limpiando mis dibujables correctamente? Para cada elemento de mi lista de mapeados, llamo a setCallBack (null) y establezco el elemento a nulo. ¿No es eso suficiente?
Desesperado por cualquier idea.
Gracias

Las imágenes se extraen de la Web, cada una de ellas desde 300 K hasta 500 K de tamaño, y se almacenan en una lista de mallas de Dratables.

El tamaño del archivo kb de la imagen que está cargando desde la web no es directamente relevante. Dado que se convierten en mapas de bits, debe calcular el ancho * alto * 4 bytes por imagen para las imágenes ARGB normales. (ancho y alto en px).

Los mapas de bits consumen montón nativo, que generalmente no se muestra en un hprof. El hprof solo debe mostrarle la cantidad de objetos, es decir, BitmapDrawables o Bitmaps que quedan.

Utilizo este código en mi aplicación para generar la memoria usada actual utilizada por la aplicación y el montón nativo:

 public static void logHeap(Class clazz) { Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576)); Double available = new Double(Debug.getNativeHeapSize())/1048576.0); Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0); DecimalFormat df = new DecimalFormat(); df.setMaximumFractionDigits(2); df.setMinimumFractionDigits(2); Log.d(APP, "debug. ================================="); Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]"); Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)"); System.gc(); System.gc(); // don't need to add the following lines, it's just an app specific handling in my app if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) { android.os.Process.killProcess(android.os.Process.myPid()); } } 

al que llamo cuando comienzo o termino una actividad durante el desarrollo.

 logHeap(this.getClass()); 

Aquí hay algunos enlaces informativos: en general, hay muchos temas sobre este tema aquí.

  • Bitmaps en Android
  • Android: Eclipse MAT no parece mostrar todos los objetos de mi aplicación

Aquí también hay una diapositiva útil de Romain Guy (ingeniero de Android Framework) sobre referencias suaves, referencias débiles, cachés simples, manejo de imágenes: http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

Aquí hay algunos consejos:

  1. ¿Usas la opción inSampleSize? Reduce el consumo de memoria si escalas imágenes. Extraño problema de memoria al cargar una imagen en un objeto Bitmap

  2. Deberías llamar a Bitmap.recycle () cuando ya no necesites imágenes. Creo que es importante en tu caso. Android: OutofMemoryError: el tamaño del bitmap excede el presupuesto de VM sin ninguna razón por la que pueda ver

La imagen que está cargando en la galería 5 o 6 podría ser demasiado grande para cargar y excederá el tamaño máximo permitido por la máquina virtual.

Será mejor que sepa que la lista convertView en los parámetros de getView siempre es null . Es decir, la galería no reutiliza la vista anterior en el interior.