Java.lang.OutOfMemoryError: el tamaño del mapa de bits supera el presupuesto de VM-Android


He desarrollado una aplicación que utiliza un montón de imágenes en Android.

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

No hay animación, no hay efectos especiales ni nada que pueda llenar la memoria. A veces los elementos de diseño pueden cambiar. Algunos son recursos de Android y otros son archivos guardados en una carpeta en la tarjeta SD.

Luego el usuario sale (el método onDestroy se ejecuta y la aplicación permanece en la memoria por la VM) y luego en algún momento el usuario vuelve a entrar.

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

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

Debo ponerlos en métodos estáticos para que no se cargan todo el tiempo? Tengo que limpiar el diseño o las imágenes utilizadas en el diseño de una manera especial?

Author: Ironman, 2009-12-22

13 answers

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

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

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

En particular, es probable que el uso de variables estáticas empeore las cosas, no las mejore. Es posible que necesite para agregar código que elimina las devoluciones de llamada cuando su aplicación vuelve a dibujar but pero de nuevo, no hay suficiente información aquí para decir con seguridad.

 70
Author: Trevor Johns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-03 22:13:10

Uno de los errores más comunes que encontré desarrollando aplicaciones Android es el "java.lang.Error OutOfMemoryError: El Tamaño del Mapa de Bits Excede el Presupuesto de VM " error. Encontré este error con frecuencia en las 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 de la VM disponible para mapas de bits.

Los mapas de bits en el diseño de actividad anterior no están correctamente desasignados por el recolector de basura porque tienen referencias cruzadas a su actividad. Después de muchos experimentos encontré una solución bastante buena para este problema.

Primero, establezca el atributo" id " en la vista principal de su diseño XML:

    <?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"
     android:id="@+id/RootView"
     >
     ...

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 vistas recursivamente y:

  1. Elimina las devoluciones de llamada en todos los elementos de diseño de fondo
  2. Elimina niños en cada viewgroup
 96
Author: hp.android,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-22 12:09:27

Para evitar este problema, puede usar el método nativo Bitmap.recycle() antes del objeto null-ing 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);
 10
Author: ddmytrenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-04 10:49:22

Me he encontrado con este problema exacto. El montón es bastante pequeño, por lo que estas imágenes pueden salirse de control bastante rápido en lo que respecta a la memoria. Una forma es darle al recolector de basura una pista para recolectar memoria en un mapa de bits 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 diagrama/tabla del ciclo de vida de la actividad en esta página para obtener más información.

 7
Author: Donn Felker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-22 22:16:10

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

" Consejos rápidos:

1) NUNCA llame al sistema.gc() usted mismo. Esto se ha propagado como una solución aquí, y no funciona. No lo hagas. Si te has dado cuenta en mi explicación, antes de obtener un error OutOfMemoryError, la JVM ya ejecuta una colección de basura, por lo que no hay razón para hacerlo de nuevo (está ralentizando tu programa). Hacer uno al final de su actividad es solo encubrir el problema. Puede hacer que el mapa de bits se ponga en la cola del finalizador más rápido, pero no hay ninguna razón por la que no haya podido simplemente llamar a reciclar en cada mapa de bits en su lugar.

2) Siempre llame a recycle() en mapas de bits que ya no necesita. Por lo menos, en el onDestroy de su actividad ir a través y reciclar todos los mapas de bits que estaba utilizando. Además, si desea que las instancias de mapa de bits se recopilen del montón de dalvik más rápido, no está de más borrar ninguna referencia al mapa de bits.

3) Llamando a recycle () y luego a System.gc () todavía puede no eliminar el mapa de bits del montón de Dalvik. NO TE PREOCUPES por esto. recycle () hizo su trabajo y liberó la memoria nativa, solo tomará algún tiempo seguir los pasos que describí anteriormente para eliminar el mapa de bits del montón de Dalvik. Esto no es un gran problema porque la gran parte de la memoria nativa ya es gratis!

4) Siempre asuma que hay un error en el último framework. 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. "

 7
Author: Aron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-11 00:52:10

Tuve exactamente el mismo problema. Después de algunas pruebas, descubrí que este error está apareciendo para el escalado de imágenes grandes. Reduje el escalado de la imagen y el problema desapareció.

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

 5
Author: GSree,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-20 07:56:19

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

  1. Utilice el contexto de la aplicación(en lugar de la actividad.esto) donde sea posible.
  2. Detén y libera tus hilos en el método de actividad onPause ()
  3. Libera tus vistas / callbacks en el método de actividad onDestroy ()
 5
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-08 06:03:39

Sugiero una manera conveniente de resolver este problema. Simplemente asigne el valor del atributo "android: configChanges" como se sigue en el Mainfest.xml para su actividad errored. así:

<activity android:name=".main.MainActivity"
              android:label="mainActivity"
              android:configChanges="orientation|keyboardHidden|navigation">
</activity>

La primera solución que di realmente había reducido la frecuencia de error de OOM a un nivel bajo. Pero, no resolvió el problema totalmente. Y entonces daré la 2da 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 / drawable de mi proyecto. Como una imagen sobrecalificada que tiene una resolución de 128X128, podría redimensionarse a 64x64, lo que también sería adecuado para mi aplicación. Y después de hacerlo con un montón de fotos, el error de OOM no vuelve a ocurrir.

 4
Author: Ranger Way,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-22 14:04:57

Yo también estoy frustrado por el error outofmemory. Y sí, yo también encontré 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 aumentaba 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 arrojaría un error outofmemory cada vez que el usuario pasara de una actividad a otra. Configurando mis elementos de diseño a null y llamando al Sistema.gc () no funcionó, tampoco reciclaba mis bitmapDrawables con getBitMap ().reciclar(). Android continuaría lanzando el error outofmemory con el primer enfoque, y lanzaría un mensaje de error canvas cada vez que intentara usar un mapa de bits reciclado con el segundo enfoque.

Tomé un tercer enfoque. Establezco todas las vistas en null 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 onPause () método, 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 llamará.

Para evitar que ocurra 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 luego finish ().

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

 3
Author: subduedjoy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-23 21:58:21

La BitmapFactory.los métodos descodificar*, discutidos en la lección Cargar mapas de bits grandes de manera eficiente, no deben ejecutarse en el subproceso principal de la interfaz de usuario si los datos de origen se leen desde el disco o una ubicación de red (o realmente cualquier fuente que no sea la memoria). El tiempo que estos datos tardan 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 subproceso de IU, el sistema marca la aplicación como no responde y el usuario tiene la opción de cerrarlo (consulte Diseñar para la capacidad de respuesta para obtener más información).

 1
Author: Li3ro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-07 13:06:56

Bueno, he intentado todo lo que encontré en Internet y ninguno de ellos funcionó. Llamando al Sistema.gc () solo reduce la velocidad de app. Reciclar mapas de bits en onDestroy tampoco funcionó para mí.

Lo único que funciona ahora es tener una lista estática de todos los mapas de bits para que los mapas de bits sobrevivan después de un reinicio. Y simplemente 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;
    }
}
 0
Author: HarryHao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-18 06:40:34

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

ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
 0
Author: Gunnar Bernstein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-20 23:22:45

FWIW, aquí hay un bitmap-cache ligero que codifiqué y he usado durante unos meses. No es todo-las-campanas-y-silbidos, 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<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();  

    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<Bitmap> 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<Bitmap> list = getList(key); 
        list.add(bitmap); 
    }

    private ArrayList<Bitmap> getList(String key) {
        ArrayList<Bitmap> list = hashtable.get(key);
        if (list==null) { 
            list = new ArrayList<Bitmap>(); 
            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();
    }

}
 0
Author: Guy Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-10 04:51:06