Reciclaje de bitmap con largeHeap habilitado

Antes de habilitar la opción largeHeap , manejaba grandes mapas de bits y consumía casi toda la memoria disponible para la aplicación, y reciclarla durante la navegación y cargar nuevas funciona en casi todo el montón disponible. Sin embargo, cuando algunas operaciones necesitan un poco más de memoria, la aplicación se bloquea. Así que habilité largeHeap=true para tener un poco más de memoria.

Pero al hacer esto tiene un comportamiento inesperado, parece que el método recycle() de mapas de bits no funciona la mayoría de las veces, y la aplicación que funciona en 58Mb de memoria (y excede a veces lanzando OutOfMemoryException ) ahora consume memoria exponencialmente y sigue creciendo ( por ahora, la prueba que realicé llegó a la memoria asignada de 231 Mb), el comportamiento esperado es que la administración de memoria sigue funcionando y la aplicación no usará más de 60 Mb.

¿Cómo puedo evitar eso? ¿O reciclar bitmaps de manera eficiente?

EDITAR: En realidad, lo hice dar un OutOfMemoryError al asignar más de 390Mb de memoria en el dispositivo. La lectura de los registros de GC_ * mostró que solo GC_FOR_ALLOC liberaba 3.8Mb a veces, pero casi nunca otras ejecuciones de GC liberaron algo.

Probablemente deberías echar un vistazo a la visualización de mapas de bits de manera eficiente, que incluye varias formas de manejar grandes mapas de bits de manera eficiente,

  • Cargando grandes mapas de bits de manera eficiente
 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; 

Esto le dará el tamaño de la imagen antes de descargarla y, sobre esa base, puede verificar el tamaño de su dispositivo y escalar utilizando calculateInSampleSize() y decodeSampledBitmapFromResource() en la explicación de documentos.

Calculando cuánto necesitamos para escalar la imagen,

  • Primera forma
 if (imageHeight > reqHeight || imageWidth > reqWidth) { if (imageWidth > imageHeight ) { inSampleSize = Math.round((float)imageHeight / (float)reqHeight); } else { inSampleSize = Math.round((float)imageWidth / (float)reqWidth); } } 
  • Segunda forma
 int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight); 

El puede configurar inSampleSize ,

  options.inSampleSize = inSampleSize; 

Entonces, finalmente asegúrate de llamar,

 options.inJustDecodeBounds = false; 

de lo contrario, devolverá Bitmap como null

  • Procesamiento de mapas de bits fuera del hilo de la interfaz de usuario

    Procesar Bitmap en el subproceso de interfaz de usuario nunca es seguro, por lo que siempre es mejor hacerlo en un subproceso en segundo plano y actualizar la interfaz de usuario después de que se complete el proceso.

  • Caching Bitmaps

    LruCache está disponible en la API 12, pero si está interesado en utilizar las versiones siguientes, también está disponible en la Biblioteca de soporte . Por lo tanto, el almacenamiento en caché de las imágenes debe hacerse de manera eficiente con eso. También puede usar DiskLruCache para las imágenes donde desee y luego permanecer por más tiempo en almacenamiento externo.

  • Borrar el caché

    A veces, cuando el tamaño de la imagen es demasiado grande, el almacenamiento en caché produce OutOfMemoryError por lo que es mejor borrar la caché cuando la imagen está fuera del scope o no se usa durante un período más largo para que otras imágenes puedan almacenarse en caché.

    Creé un ejemplo de demostración para el mismo, puedes descargarlo desde aquí

Su caso se comporta como se esperaba Antes de Honeycomb, recycle() liberaba incondicionalmente la memoria. Pero en 3.0 y superiores, los mapas de bits son parte de la memoria recolectada basura normal. Tiene suficiente RAM en el dispositivo, permitió que la JVM asignara más del límite de 58M, ahora el recolector de basura está satisfecho y no tiene ningún incentivo para reclamar la memoria ocupada por sus bitmaps.

Puede verificar esto ejecutando un emulador con cantidad controlada de RAM, o cargar algún servicio que consum memoria en su dispositivo: GC se pondrá a trabajar. Puede usar DDMS para investigar más a fondo su uso de memoria.

Puede probar algunas soluciones para la administración de la memoria de bitmap: los mapas de bits en la memoria de bitmap de Android se filtran http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/ , pero comience con la versión oficial Consejos sobre mapas de bits de Android , como se explica en la respuesta detallada de @Lalit Poptani .

Tenga en cuenta que mover los mapas de bits a la memoria OpenGL como texturas tiene algunas implicaciones de rendimiento (pero perfecto si finalmente renderiza estos mapas de bits a través de OpenGL). Tanto las texturas como las soluciones malloc requieren que libere explícitamente la memoria de bitmap que ya no usa.

Definitivamente la respuesta de @Lalit Poptani es la manera de hacerlo, realmente debe escalar sus Bitmaps si son muy grandes. Una forma preferible es que esto se haga server-side si esto es posible, ya que también reducirá el tiempo de NetworkOperation .

Con respecto a la implementación de una MemoryCache y una DiskCache esta es la mejor manera de hacerlo, pero aún así recomendaría usar una biblioteca existente, que hace exactamente eso ( Ignition ) y se ahorrará mucho tiempo, y también mucho de pérdidas de memoria, ya que debido a que su Heap no se vacía después de GC , puedo suponer que probablemente también tenga algunas memory leaks .

Para abordar su dilema, creo que este es el comportamiento esperado.

Si desea liberar memoria, puede ocasionalmente llamar a System.gc() , pero en realidad debería dejar que administre la recolección de basura en su mayor parte.

Lo que recomiendo es mantener un caché simple (url / filename a bitmap) de algún tipo que haga un seguimiento de su propio uso de memoria al calcular el número de bytes que ocupa cada Bitmap.

 /** * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config * @param width * @param height * @param config * @return */ public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){ long pixels=width*height; switch(config){ case ALPHA_8: // 1 byte per pixel return pixels; case ARGB_4444: // 2 bytes per pixel, but depreciated return pixels*2; case ARGB_8888: // 4 bytes per pixel return pixels*4; case RGB_565: // 2 bytes per pixel return pixels*2; default: return pixels; } } 

Luego consulta cuánta memoria está usando la aplicación y cuánto está disponible, tal vez tome la mitad de eso y trate de mantener el tamaño total de la caché de imágenes debajo de eso, simplemente eliminando (desreferenciando) las imágenes antiguas de su lista cuando se acerque contra este límite, no reciclando . Deje que el recolector de basura limpie los mapas de bits cuando ambos se eliminen de su caché y no los use ninguna vista.

 /** * Calculates and adjusts the cache size based on amount of memory available and average file size * @return */ synchronized private int calculateCacheSize(){ if(this.cachedBitmaps.size()>0){ long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory); long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size(); this.bitmapCacheSize = (int) (maxAllocation/avgSize); } return this.bitmapCacheSize; } 

Le recomendaría que se mantenga alejado del uso de recycle() , ocasiona una gran cantidad de excepciones intermitentes (como cuando las vistas aparentemente finalizadas intentan acceder a bitmaps reciclados) y, en general, parece tener errores.

Debes tener mucho cuidado al manejar bitmaps en Android. Permítanme reformular eso: hay que tener cuidado con el manejo de bitmaps incluso en un sistema con 4 gigas de RAM. ¿Qué tan grandes son estos tipos y tienes mucho? Es posible que deba cortarlo y colocarlo en mosaico si es grande. Recuerde que utiliza la RAM de video, que es un animal diferente al RAM del sistema.

Pre-Honeycomb, los mapas de bits se asignaron en la capa C ++, por lo que el uso de la RAM era invisible para Java y el recolector de basura no podía acceder. Un bitmap sin comprimir de 3 MP con el espacio de color RGB24 usa alrededor de 9-10 megabytes (alrededor de 2048×1512). Por lo tanto, las imágenes más grandes pueden llenar fácilmente tu montón. También recuerde que en cualquier cosa que se esté utilizando para la RAM de video (a veces RAM dedicada, a veces compartida con el sistema), los datos generalmente se almacenan sin comprimir.

Básicamente, si se dirige a dispositivos pre-Honeycomb, casi tendrá que administrar el objeto Bitmap como si estuviera codificando un progtwig C ++. Ejecutar el reciclaje de bitmap () onDestory () generalmente funciona si no hay muchas imágenes, pero si tiene muchas imágenes en la pantalla, es posible que deba manejarlas sobre la marcha. Además, si inicia otra actividad, es posible que deba considerar incluir lógica en onPause () y onResume ().

También puede almacenar en caché las imágenes usando el sistema de archivos Android o SQLite cuando no están en la RAM de video. Es posible que pueda salirse con la caché en la memoria RAM si está utilizando un formato como .jpg o .png con una gran cantidad de datos repetidos /