java.lang.OutOfMemoryError: el tamaño del bitmap excede el presupuesto de VM – Android

Desarrollé una aplicación que usa muchas imágenes en Android.

La aplicación se ejecuta una vez, llena la información en la pantalla ( Layouts , Textviews , Textviews , Textviews , etc.) y el usuario lee la información.

No hay animación, ningún efecto especial ni nada que pueda llenar la memoria. A veces, los descartables pueden cambiar. Algunos son recursos de Android y otros son archivos guardados en una carpeta en el SDCARD.

A continuación, el usuario se cierra (el método onDestroy se ejecuta y la aplicación mantiene la memoria en la máquina virtual) y luego, en algún momento, el usuario vuelve a ingresar.

Cada vez que el usuario ingresa a la aplicación, puedo ver que la memoria crece cada vez más hasta que el usuario obtiene java.lang.OutOfMemoryError .

Entonces, ¿cuál es la mejor / correcta forma de manejar muchas imágenes?

¿Debería ponerlos en métodos estáticos para que no estén cargados todo el tiempo? ¿Tengo que limpiar el diseño o las imágenes utilizadas en el diseño de una manera especial?

Parece que tienes una pérdida de memoria. El problema no es manejar muchas imágenes, sino que sus imágenes no se desasignan cuando se destruye su actividad.

Es difícil decir por qué sucede esto sin mirar su código. Sin embargo, este artículo tiene algunos consejos que pueden ayudar:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

En particular, el uso de variables estáticas probablemente empeore las cosas, no mejorará. Es posible que necesite agregar un código que elimine las devoluciones de llamada cuando su aplicación se vuelva a dibujar, pero nuevamente, no hay suficiente información aquí para decirlo con certeza.

Uno de los errores más comunes que encontré al desarrollar aplicaciones de Android es el error “java.lang.OutOfMemoryError: Tamaño de bitmap supera el presupuesto de VM”. Encontré este error con frecuencia en actividades que usan muchos mapas de bits después de cambiar la orientación: la actividad se destruye, se crea de nuevo y los diseños se “inflan” desde el XML que consume la memoria VM disponible para mapas de bits.

Los mapas de bits en el diseño de la actividad anterior no se desasignan correctamente por el recolector de basura porque han cruzado referencias a su actividad. Después de muchos experimentos, encontré una solución bastante buena para este problema.

Primero, configure el atributo “id” en la vista principal de su diseño XML:

    ... 

Luego, en el método onDestroy() de su Actividad, llame al método unbindDrawables() pasando una referencia a la Vista padre y luego haga un System.gc() .

  @Override protected void onDestroy() { super.onDestroy(); unbindDrawables(findViewById(R.id.RootView)); System.gc(); } private void unbindDrawables(View view) { if (view.getBackground() != null) { view.getBackground().setCallback(null); } if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { unbindDrawables(((ViewGroup) view).getChildAt(i)); } ((ViewGroup) view).removeAllViews(); } } 

Este método unbindDrawables() explora el árbol de vista recursivamente y:

  1. Elimina las devoluciones de llamada en todos los diseños arrastrables
  2. Remueve niños en cada grupo de visión

Para evitar este problema, puede utilizar el método nativo Bitmap.recycle() antes de Bitmap.recycle() objeto Bitmap (o establecer otro valor). Ejemplo:

 public final void setMyBitmap(Bitmap bitmap) { if (this.myBitmap != null) { this.myBitmap.recycle(); } this.myBitmap = bitmap; } 

Y luego puedes cambiar myBitmap sin llamar a System.gc() como:

 setMyBitmap(null); setMyBitmap(anotherBitmap); 

Me encontré con este problema exacto. El montón es bastante pequeño, por lo que estas imágenes pueden perder el control rápidamente en lo que respecta a la memoria. Una forma es dar al recolector de basura una pista para recolectar memoria en un bitmap llamando a su método de reciclaje .

Además, no se garantiza que se llame al método onDestroy. Es posible que desee mover esta lógica / limpieza a la actividad onPause. Consulte el diagtwig / tabla del ciclo de vida de la actividad en esta página para obtener más información.

Esta explicación podría ayudar: http://code.google.com/p/android/issues/detail?id=8488#c80

“Consejos rápidos:

1) NUNCA llame a System.gc () usted mismo. Esto se ha propagado como una solución aquí, y no funciona. No lo hagas. Si se dio cuenta en mi explicación, antes de obtener un OutOfMemoryError, la JVM ya ejecuta una recolección de basura, por lo que no hay ninguna razón para volver a hacer una (se ralentiza el progtwig). Hacer uno al final de tu actividad es simplemente encubrir el problema. Puede causar que el bitmap se coloque en la cola del finalizador más rápido, pero no hay ninguna razón por la que no haya podido simplemente invocar reciclar en cada bitmap en su lugar.

2) Siempre llame a recycle () en bitmaps que ya no necesita. Por lo menos, en el proceso de su actividad, revise y recicle todos los mapas de bits que estaba usando. Además, si desea que las instancias de bitmap se recopilen más rápido del montón de dalvik, no está de más aclarar las referencias al bitmap.

3) Llamar a recycle () y luego a System.gc () aún podría no eliminar el bitmap del montón de Dalvik. NO SE PREOCUPE por esto. recycle () hizo su trabajo y liberó la memoria nativa, solo tomará un tiempo para seguir los pasos que describí anteriormente para eliminar realmente el bitmap del montón de Dalvik. ¡Esto NO es un gran problema porque la gran cantidad de memoria nativa ya es gratis!

4) Siempre asum que hay un error en el framework por última vez. Dalvik está haciendo exactamente lo que se supone que debe hacer. Puede que no sea lo que esperas o lo que quieres, pero es cómo funciona. ”

Tuve exactamente el mismo problema. Después de algunas pruebas, descubrí que este error está apareciendo para escalar imágenes de gran tamaño. Reduje la escala de la imagen y el problema desapareció.

PD Al principio traté de reducir el tamaño de la imagen sin reducir la escala de la imagen. Eso no detuvo el error.

Los siguientes puntos realmente me ayudaron mucho. Puede haber otros puntos también, pero estos son muy cruciales:

  1. Use el contexto de la aplicación (en lugar de activity.this) donde sea posible.
  2. Detenga y libere sus hilos en el método de actividad onPause ()
  3. Publica tus opiniones / devoluciones de llamada en el método de actividad onDestroy ()

Sugiero una forma conveniente de resolver este problema. Simplemente asigne el valor de atributo “android: configChanges” como se sigue en Mainfest.xml para su actividad con errores. Me gusta esto:

   

la primera solución que di realmente redujo la frecuencia del error OOM a un nivel bajo. Pero, no resolvió el problema totalmente. Y luego daré la segunda solución:

Como detalló el OOM, he usado demasiada memoria de tiempo de ejecución. Por lo tanto, reduzco el tamaño de la imagen en ~ / res / dibujable de mi proyecto. Tal como una imagen sobrecalificada que tiene una resolución de 128X128, podría cambiar el tamaño a 64×64, que también sería adecuado para mi aplicación. Y después de hacerlo con una stack de imágenes, el error OOM no vuelve a ocurrir.

Yo también estoy frustrado por el error fuera de la memoria. Y sí, también descubrí que este error aparece mucho al escalar imágenes. Al principio intenté crear tamaños de imagen para todas las densidades, pero descubrí que esto aumentó sustancialmente el tamaño de mi aplicación. Así que ahora solo estoy usando una imagen para todas las densidades y escalando mis imágenes.

Mi aplicación arrojaba un error de memoria cuando el usuario pasaba de una actividad a otra. Establecer mis drawable a null y llamar a System.gc () no funcionó, ni tampoco reciclar mis bitmapDrawables con getBitMap (). Recycle (). Android continuaría arrojando el error de memoria con el primer enfoque, y lanzaría un mensaje de error de canvas siempre que intentara usar un bitmap reciclado con el segundo enfoque.

Tomé un tercer acercamiento. Establecí todas las vistas en nulo y el fondo en negro. Hago esta limpieza en mi método onStop (). Este es el método que se llama tan pronto como la actividad ya no es visible. Si lo haces en el método onPause (), los usuarios verán un fondo negro. No es ideal. En cuanto a hacerlo en el método onDestroy (), no hay garantía de que se llame.

Para evitar que aparezca una pantalla negra si el usuario presiona el botón Atrás en el dispositivo, recargo la actividad en el método onRestart () llamando a los métodos startActivity (getIntent ()) y finish ().

Nota: no es realmente necesario cambiar el fondo a negro.

Los métodos BitmapFactory.decode *, discutidos en la lección Cargar mapas de bits de manera eficiente , no deberían ejecutarse en el hilo principal de la interfaz de usuario si los datos de origen se leen desde el disco o desde una ubicación de red (o desde cualquier fuente que no sea la memoria). El tiempo que tardan estos datos en cargarse es impredecible y depende de una variedad de factores (velocidad de lectura desde el disco o la red, tamaño de la imagen, potencia de la CPU, etc.). Si una de estas tareas bloquea el hilo de la interfaz de usuario, el sistema marca su aplicación como no receptiva y el usuario tiene la opción de cerrarla (para obtener más información, consulte Diseño de capacidad de respuesta).

Bueno, probé todo lo que encontré en Internet y ninguno de ellos funcionó. Llamar a System.gc () solo reduce la velocidad de la aplicación. Reciclar bitmaps en onDestroy tampoco funcionó para mí.

Lo único que funciona ahora es tener una lista estática de todo el bitmap para que los mapas de bits sobrevivan después de un reinicio. Y solo use los mapas de bits guardados en lugar de crear nuevos cada vez que se reinicie la actividad.

En mi caso, el código se ve así:

 private static BitmapDrawable currentBGDrawable; if (new File(uriString).exists()) { if (!uriString.equals(currentBGUri)) { freeBackground(); bg = BitmapFactory.decodeFile(uriString); currentBGUri = uriString; bgDrawable = new BitmapDrawable(bg); currentBGDrawable = bgDrawable; } else { bgDrawable = currentBGDrawable; } } 

Tuve el mismo problema solo al cambiar las imágenes de fondo con tamaños razonables. Obtuve mejores resultados al configurar ImageView en nulo antes de poner una nueva imagen.

 ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage); ivBg.setImageDrawable(null); ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture)); 

FWIW, aquí hay un bitmap-caché liviano que he codificado y he usado durante unos meses. No son todas las campanas y silbatos, así que lea el código antes de usarlo.

 /** * Lightweight cache for Bitmap objects. * * There is no thread-safety built into this class. * * Note: you may wish to create bitmaps using the application-context, rather than the activity-context. * I believe the activity-context has a reference to the Activity object. * So for as long as the bitmap exists, it will have an indirect link to the activity, * and prevent the garbaage collector from disposing the activity object, leading to memory leaks. */ public class BitmapCache { private Hashtable> hashtable = new Hashtable>(); private StringBuilder sb = new StringBuilder(); public BitmapCache() { } /** * A Bitmap with the given width and height will be returned. * It is removed from the cache. * * An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen. * * Note that thread-safety is the caller's responsibility. */ public Bitmap get(int width, int height, Bitmap.Config config) { String key = getKey(width, height, config); ArrayList list = getList(key); int listSize = list.size(); if (listSize>0) { return list.remove(listSize-1); } else { try { return Bitmap.createBitmap(width, height, config); } catch (RuntimeException e) { // TODO: Test appendHockeyApp() works. App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height); throw e ; } } } /** * Puts a Bitmap object into the cache. * * Note that thread-safety is the caller's responsibility. */ public void put(Bitmap bitmap) { if (bitmap==null) return ; String key = getKey(bitmap); ArrayList list = getList(key); list.add(bitmap); } private ArrayList getList(String key) { ArrayList list = hashtable.get(key); if (list==null) { list = new ArrayList(); hashtable.put(key, list); } return list; } private String getKey(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); Config config = bitmap.getConfig(); return getKey(width, height, config); } private String getKey(int width, int height, Config config) { sb.setLength(0); sb.append(width); sb.append("x"); sb.append(height); sb.append(" "); switch (config) { case ALPHA_8: sb.append("ALPHA_8"); break; case ARGB_4444: sb.append("ARGB_4444"); break; case ARGB_8888: sb.append("ARGB_8888"); break; case RGB_565: sb.append("RGB_565"); break; default: sb.append("unknown"); break; } return sb.toString(); } }