2009-12-23 11 views
30

He estado haciendo muchas búsquedas y sé que muchas otras personas están experimentando los mismos problemas de memoria OOM con BitmapFactory. Mi aplicación solo muestra una memoria total disponible de 4MB usando Runtime.getRuntime ().totalMemory(). Si el límite es de 16 MB, ¿por qué no crece la memoria total de para dejar espacio para el mapa de bits? En su lugar, arroja un error.BitmapFactory OOM me está volviendo loco

Asimismo, no entiendo que si tengo 1.6MB de memoria libre de acuerdo a Runtime.getRuntime().freeMemory() ¿por qué recibo un error que dice "VM no dejará que nos asignamos 614400 bytes"? Me parece que tengo suficiente memoria disponible.

Mi aplicación está completa, excepto por este problema, que desaparece cuando I reinicio del teléfono para que mi aplicación sea la única en ejecución. Estoy usando un HTC Hero para probar dispositivos (Android 1.5).

En este momento estoy pensando que la única forma de evitar esto es de alguna manera evite usar BitmapFactory.

¿Alguien tiene alguna idea sobre esto o una explicación de por qué VM no va a asignar 614KB cuando hay 1.6MB de memoria libre?

+0

[this] (http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object/14731953#14731953) ¡puede ayudar! –

Respuesta

2

1.6 MB de memoria parece mucho, pero podría ser el caso de que la memoria esté tan fragmentada que no pueda asignar un gran bloque de memoria de una vez (todavía suena muy extraño).

Una causa común de OOM durante el uso de recursos de imágenes es cuando se descomprimen las imágenes JPG, PNG, GIF con resoluciones realmente altas. Debe tener en cuenta que todos estos formatos están muy bien comprimidos y ocupan muy poco espacio, pero una vez que carga las imágenes en el teléfono, la memoria que van a usar es algo así como width * height * 4 bytes. Además, cuando se inicia la descompresión, es necesario cargar algunas otras estructuras de datos auxiliares para el paso de decodificación.

45

[Tenga en cuenta que (como dice CommonsWare señala a continuación) todo el enfoque en esta respuesta solo aplica hasta 2.3.x (Gingerbread) inclusive. A partir de Honeycomb, los datos de mapa de bits se asignan en el montón de VM.]

Los datos de mapa de bits no se asignan en el montón de VM. Hay una referencia en el montón de VM (que es pequeño), pero la biblioteca subyacente de Skia asigna los datos reales en el montón nativo.

Lamentablemente, si bien la definición de BitmapFactory.decode ...() dice que devuelve null si no se ha podido descodificar los datos de la imagen, la implementación Skia (o más bien el pegamento JNI entre el código Java y Skia) registra el mensaje que está viendo ("VM no nos permitirá asignar xxxx bytes") y luego lanza una excepción OutOfMemory con el mensaje engañoso "el tamaño del mapa de bits excede el presupuesto de VM".

El problema no está en el montón de VM, sino en el montón nativo. El montón de Native se comparte entre las aplicaciones en ejecución, por lo que la cantidad de espacio libre depende de qué otras aplicaciones se estén ejecutando y del uso de su mapa de bits. Pero, dado que BitmapFactory no volverá, necesita una forma de averiguar si la llamada tendrá éxito antes de hacerlo.

Existen rutinas para supervisar el tamaño del montón nativo (consulte los métodos getNative de la clase Debug). Sin embargo, he encontrado que getNativeHeapFreeSize() y getNativeHeapSize() no son confiables. Entonces, en una de mis aplicaciones que crea dinámicamente una gran cantidad de bitmaps, hago lo siguiente.

El tamaño del montón nativo varía según la plataforma.Por lo tanto, al inicio, verificamos el tamaño máximo de almacenamiento dinámico de máquinas virtuales permitido para determinar el tamaño de almacenamiento dinámico nativo máximo permitido. [Los números mágicos se determinaron mediante ensayos con 2.1 y 2.2, y pueden ser diferentes en otros niveles de la API.]

long mMaxVmHeap  = Runtime.getRuntime().maxMemory()/1024; 
long mMaxNativeHeap = 16*1024; 
if (mMaxVmHeap == 16*1024) 
    mMaxNativeHeap = 16*1024; 
else if (mMaxVmHeap == 24*1024) 
    mMaxNativeHeap = 24*1024; 
else 
    Log.w(TAG, "Unrecognized VM heap size = " + mMaxVmHeap); 

A continuación, cada vez que tenemos que llamar BitmapFactory que preceden a la llamada de un cheque de la forma.

long sizeReqd  = bitmapWidth * bitmapHeight * targetBpp/8; 
long allocNativeHeap = Debug.getNativeHeapAllocatedSize(); 
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap) 
{ 
    // Do not call BitmapFactory… 
} 

Tenga en cuenta que la heapPad es un número mágico para tener en cuenta el hecho de que a) la notificación de tamaño de la pila nativa es "suave" y b) que quiere dejar algo de espacio en el montón nativo para otras aplicaciones. Estamos corriendo con un pad 3 * 1024 * 1024 (es decir, 3Mbytes) actualmente.

+0

Un enfoque muy interesante y práctico. – BonanzaDriver

+0

Publicación interesante. ¿Tiene alguna prueba/fuente para este ?: "El montón Native se comparte entre las aplicaciones en ejecución, por lo que la cantidad de espacio libre depende de qué otras aplicaciones se estén ejecutando y su uso del mapa de bits". – mibollma

+0

Eso se basó en la lectura de la fuente Skia y, en particular, en cómo maneja bitmap.recycle(). Pero en retrospectiva, aunque no veo referencias/manejo por aplicación en ese código (que es lo que me llevó a esa afirmación), ahora creo que es demasiado fuerte/incorrecto.El montón nativo se asignará por aplicación, dependiendo del manejo normal de la memoria por proceso de la máquina virtual, y por lo tanto, el código Skia no tiene que preocuparse por problemas de aplicación/proceso. – Torid

0

Aunque normalmente no tiene sentido detectar un error porque generalmente son arrojados solo por el vm pero en este caso particular, el error es arrojado por el código jni pegamento por lo que es muy sencillo manejar casos donde no se pudo cargar la imagen: simplemente atrapa el OutOfMemoryError.

0

Aunque esta es una respuesta de alto nivel, el problema para mí resultó ser la aceleración de hardware en todas mis vistas. La mayoría de mis vistas tienen una manipulación de mapa de bits personalizada, que calculé que era el origen del gran tamaño de heap nativo, pero de hecho al deshabilitar la aceleración de hardware, el uso de heap nativo se redujo por un factor de 4.

Parece como aunque la aceleración de hardware hará todo tipo de almacenamiento en caché en tus vistas, creando mapas de bits propios, y dado que todos los mapas de bits comparten el montón nativo, el tamaño de la asignación puede crecer de manera espectacular.

+0

¿Y cómo hacemos eso? ... –

+1

@Henrique Sousa: agregue android: hardwareAccelerated = "falso" a su actividad en AndroidManifest.xml – samgak

+0

Sorprendentemente esto resolvió el problema ... No puedo creer lo mal que Google documentó este uso cuádruple de la memoria ! –

2

Parece que los problemas indicados en Torid's answer se han resuelto en las versiones más recientes de Android.

Sin embargo, si está utilizando un caché de imágenes (uno especializado o incluso un HashMap normal), es muy fácil obtener este error creando una pérdida de memoria.

En mi experiencia, si inadvertidamente se aferran a sus Bitmap referencias y crear una pérdida de memoria, el error de OP (una referencia a los métodos BitmapFactory y nativas) es la que se colgará su aplicación (hasta ICS - 14 y +?)

Para evitar esto, haz que tu "sueltes" tus Bitmaps. Esto significa usar SoftReferences en el nivel final de su caché, de modo que Bitmaps pueda obtener la recolección de basura. Esto debería funcionar, pero si sigues teniendo bloqueos, puedes intentar marcar explícitamente ciertos mapas de bits para recopilarlos usando bitmap.recycle(), solo recuerda nunca regresar un mapa de bits para usar en tu aplicación si bitmap.isRecycled().

Como acotación al margen, LinkedHashMaps son una gran herramienta para implementar fácilmente bastante buenas estructuras de caché, especialmente si se combina referencias duras y blandas como en this example (starting line 308) ... pero utilizando referencias duro es también cómo puede conseguirse en la memoria situaciones de fuga si te equivocas.

Cuestiones relacionadas