Extraño problema de falta de memoria al cargar una imagen en un objeto de mapa de bits

1289

Tengo una vista de lista con un par de botones de imagen en cada fila. Cuando hace clic en la fila de la lista, se inicia una nueva actividad. Tuve que crear mis propias pestañas debido a un problema con el diseño de la cámara. La actividad que se inicia para el resultado es un mapa. Si hago clic en mi botón para iniciar la vista previa de la imagen (cargar una imagen de la tarjeta SD), la aplicación vuelve de la actividad a la listviewactividad al controlador de resultados para relanzar mi nueva actividad, que no es más que un widget de imagen.

La vista previa de la imagen en la vista de lista se realiza con el cursor y ListAdapter. Esto lo hace bastante simple, pero no estoy seguro de cómo puedo poner una imagen redimensionada (es decir, un tamaño de bit más pequeño, no un píxel, como el srcbotón de imagen sobre la marcha. Así que simplemente cambié el tamaño de la imagen que salió de la cámara del teléfono.

El problema es que aparece un error de falta de memoria cuando intenta regresar y volver a iniciar la segunda actividad.

  • ¿Hay alguna manera de construir el adaptador de lista fácilmente fila por fila, donde pueda cambiar el tamaño sobre la marcha (en cuanto a bits )?

Esto sería preferible ya que también necesito hacer algunos cambios en las propiedades de los widgets / elementos en cada fila ya que no puedo seleccionar una fila con la pantalla táctil debido al problema de enfoque. ( Puedo usar la bola del rodillo ) .

  • Sé que puedo hacer un cambio de tamaño fuera de banda y guardar mi imagen, pero eso no es realmente lo que quiero hacer, pero algún código de muestra para eso sería bueno.

Tan pronto como desactivé la imagen en la vista de lista, funcionó bien nuevamente.

FYI: Así es como lo estaba haciendo:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

Donde R.id.imagefilenameesta a ButtonImage.

Aquí está mi LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

También tengo un nuevo error al mostrar una imagen:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Chrispix
fuente
8
Resolví esto evitando Bitmap.decodeStream o decodeFile y usando el método BitmapFactory.decodeFileDescriptor.
Fraggle
1
También me enfrenté a un problema similar hace un par de semanas y lo resolví reduciendo las imágenes hasta el punto óptimo. He escrito un enfoque completo en mi blog codingjunkiesforum.wordpress.com/2014/06/12/… y he subido un proyecto de muestra completo con código propenso a OOM frente a código de prueba de OOM athttps: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat
55
La respuesta aceptada a esta pregunta se está discutiendo en meta
rene el
44
Esto sucede cuando no lees las guías para desarrolladores de Android
Pedro Varela
2
Esto sucede debido a la mala arquitectura de Android. Debería cambiar el tamaño de las imágenes como ios y UWP hace esto. No tengo que hacer esto yo mismo. Los desarrolladores de Android se acostumbran a ese infierno y piensan que funciona como debería.
Acceso denegado el

Respuestas:

651

La clase de formación de Android , " Mostrar mapas de bits de manera eficiente ", ofrece información excelente para comprender y tratar la excepción java.lang.OutOfMemoryError: bitmap size exceeds VM budgetal cargar mapas de bits.


Leer el mapa de bits Dimensiones y tipo

La BitmapFactoryclase proporciona varios métodos de decodificación ( decodeByteArray(), decodeFile(), decodeResource(), etc.) para la creación de una Bitmapde varias fuentes. Elija el método de decodificación más apropiado según su fuente de datos de imagen. Estos métodos intentan asignar memoria para el mapa de bits construido y, por lo tanto, pueden resultar fácilmente en una OutOfMemoryexcepción. Cada tipo de método de decodificación tiene firmas adicionales que le permiten especificar opciones de decodificación a través de la BitmapFactory.Optionsclase. Ajuste de la inJustDecodeBoundspropiedad para trueal decodificar evita la asignación de memoria, volviendo nullpara el objeto de mapa de bits, pero entorno outWidth, outHeighty outMimeType. Esta técnica le permite leer las dimensiones y el tipo de los datos de la imagen antes de la construcción (y la asignación de memoria) del mapa de bits.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Para evitar java.lang.OutOfMemoryexcepciones, verifique las dimensiones de un mapa de bits antes de decodificarlo, a menos que confíe absolutamente en la fuente para proporcionarle datos de imagen de tamaño predecible que se ajusten cómodamente a la memoria disponible.


Cargue una versión reducida en la memoria

Ahora que se conocen las dimensiones de la imagen, se pueden usar para decidir si la imagen completa se debe cargar en la memoria o si se debe cargar una versión submuestreada. Aquí hay algunos factores a considerar:

  • Uso de memoria estimado de cargar la imagen completa en la memoria.
  • La cantidad de memoria que está dispuesto a comprometer para cargar esta imagen, dado cualquier otro requisito de memoria de su aplicación.
  • Dimensiones del componente ImageView o UI de destino en el que se cargará la imagen.
  • Tamaño de pantalla y densidad del dispositivo actual.

Por ejemplo, no vale la pena cargar una imagen de 1024x768 píxeles en la memoria si eventualmente se mostrará en una miniatura de 128x96 píxeles en un ImageView.

A decir que el decodificador submuestra de la imagen, la carga de una versión más pequeña en la memoria, juego inSampleSizea trueen su BitmapFactory.Optionsobjeto. Por ejemplo, una imagen con resolución 2048x1536 que se decodifica con un inSampleSizede 4 produce un mapa de bits de aproximadamente 512x384. Al cargar esto en la memoria se usan 0,75 MB en lugar de 12 MB para la imagen completa (suponiendo una configuración de mapa de bits de ARGB_8888). Aquí hay un método para calcular un valor de tamaño de muestra que es una potencia de dos en función del ancho y la altura del objetivo:

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Nota : Se calcula una potencia de dos valores porque el decodificador utiliza un valor final redondeando a la potencia de dos más cercana, según la inSampleSizedocumentación.

Para usar este método, primero decodifique con inJustDecodeBoundsset to true, pase las opciones y luego decodifique nuevamente usando el nuevo inSampleSizevalor y inJustDecodeBoundsconfigure en false:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Este método facilita la carga de un mapa de bits de tamaño arbitrariamente grande en un ImageViewque muestra una miniatura de 100x100 píxeles, como se muestra en el siguiente código de ejemplo:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Puede seguir un proceso similar para decodificar mapas de bits de otras fuentes, sustituyendo el BitmapFactory.decode*método apropiado según sea necesario.

Sazid
fuente
21
Esta respuesta se está discutiendo en meta
rene el
99
Esta respuesta (excepto la información alcanzada a través del enlace) no ofrece mucha solución en cuanto a una respuesta. Las partes importantes del enlace deben fusionarse con la pregunta.
FallenAngel
77
Esta respuesta, como la pregunta y las otras respuestas, son Wiki de la comunidad, por lo que es algo que la comunidad puede solucionar editando, algo que no requiere la intervención del moderador.
Martijn Pieters
El enlace actual al contenido y al soporte de Kotlin se puede encontrar en: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr
891

Para corregir el error OutOfMemory, debe hacer algo como esto:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Esta inSampleSizeopción reduce el consumo de memoria.

Aquí hay un método completo. Primero lee el tamaño de la imagen sin decodificar el contenido en sí. Luego encuentra el mejor inSampleSizevalor, debe ser una potencia de 2, y finalmente la imagen se decodifica.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}
Fedor
fuente
31
Tenga en cuenta que 10 puede no ser el mejor valor para inSampleSize, sin embargo, la documentación sugiere usar poderes de 2.
Mirko N.
70
Estoy enfrentando el mismo problema que Chrispix, pero no creo que la solución aquí realmente resuelva el problema, sino que lo esquiva. Cambiar el tamaño de la muestra reduce la cantidad de memoria utilizada (a costa de la calidad de la imagen, que probablemente sea adecuada para una vista previa de la imagen), pero no evitará la excepción si se decodifica una secuencia de imágenes lo suficientemente grande, o si se requieren múltiples secuencias de imágenes descifrado. Si encuentro una solución mejor (y puede que no haya una), publicaré una respuesta aquí.
Flynn81
44
Solo necesita un tamaño apropiado para que coincida con la pantalla en densidad de píxeles, para acercar y así puede tomar una muestra de la imagen a una densidad más alta.
stealthcopter
44
REQUIRED_SIZE es el nuevo tamaño al que desea escalar.
Fedor
8
Esta solución me ayudó, pero la calidad de imagen es terrible. Estoy usando un viewfilpper para mostrar las imágenes ¿alguna sugerencia?
user1106888
373

He hecho una pequeña mejora en el código de Fedor. Básicamente hace lo mismo, pero sin el bucle while (en mi opinión) feo y siempre resulta en una potencia de dos. Felicitaciones a Fedor por hacer la solución original, me quedé atrapado hasta que encontré la suya, y luego pude hacer esta :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}
Thomas Vervest
fuente
40
Sí, tienes razón mientras no es tan hermosa. Solo intenté dejarlo claro a todos. Gracias por tu codigo.
Fedor
10
@Thomas Vervest: hay un gran problema con ese código. ^ no eleva 2 a una potencia, xors 2 con el resultado. Desea Math.pow (2.0, ...). De lo contrario, esto se ve bien.
DougW
66
¡Oh, esa es muy buena! Lo malo, lo corregiré de inmediato, ¡gracias por la respuesta!
Thomas Vervest
8
Está creando dos nuevos FileInputStreams, uno para cada llamada al BitmapFactory.decodeStream(). ¿No tiene que guardar una referencia a cada uno de ellos para que puedan cerrarse en un finallybloque?
matsev
1
@Babibu La documentación no indica que la transmisión está cerrada para usted, por lo tanto, supongo que aún debería estar cerrada. Una discusión interesante y relacionada se puede encontrar aquí . Tenga en cuenta el comentario de Adrian Smith, que se relaciona directamente con nuestro debate.
Thomas Vervest
233

Vengo de la experiencia de iOS y me sentí frustrado al descubrir un problema con algo tan básico como cargar y mostrar una imagen. Después de todo, todos los que tienen este problema están tratando de mostrar imágenes de tamaño razonable. De todos modos, aquí están los dos cambios que solucionaron mi problema (e hicieron que mi aplicación respondiera muy bien).

1) Cada vez que lo haga BitmapFactory.decodeXYZ(), asegúrese de pasar de una BitmapFactory.Optionsa inPurgeableserie a true(y preferiblemente con inInputShareabletambién establecer a true).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888). Quiero decir NUNCA! Nunca he tenido esa cosa que no genera un error de memoria después de algunos pases. Ninguna cantidad de recycle(), System.gc(), lo ayudó. Siempre planteó excepciones. La otra forma en que realmente funciona es tener una imagen ficticia en sus elementos dibujables (u otro mapa de bits que decodificó usando el paso 1 anterior), cambiar el tamaño a lo que desee, luego manipular el mapa de bits resultante (como pasarlo a un lienzo para más diversión) Por lo tanto, lo que se debe usar en su lugar es: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Si por alguna razón DEBE usar el método de creación de fuerza bruta, entonces al menos pase Config.ARGB_4444.

Esto está casi garantizado para ahorrarle horas, si no días. Todo lo que se habla sobre escalar la imagen, etc., no funciona realmente (a menos que considere una solución para obtener un tamaño incorrecto o una imagen degradada).

Efraín
fuente
22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;y Bitmap.createScaledBitmap(srcBitmap, width, height, false);resolvió mi problema que tenía con la excepción de memoria insuficiente en Android 4.0.0. ¡Gracias amigo!
Jan-Terje Sørensen
55
En la llamada Bitmap.createScaledBitmap (), probablemente debería usar true como parámetro de marca. De lo contrario, la calidad de la imagen no será uniforme cuando se amplíe. Compruebe este hilo stackoverflow.com/questions/2895065/…
rOrlig
11
Ese es realmente un consejo fabuloso. Ojalá pudiera darte un +1 extra por llevar a Google a la tarea por este error increíblemente rinky dink. Quiero decir ... si no es un error, entonces la documentación realmente necesita tener algunos letreros de neón que destellen seriamente y digan "ASÍ ES CÓMO PROCESAS LAS FOTOS", porque he estado luchando con esto durante 2 años y ahora acabo de encontrar esta publicación. Gran descubrimiento.
Yevgeny Simkin
Reducir sus imágenes definitivamente ayuda, pero este es un paso importante y lo que finalmente resolvió este problema para mí. El problema con solo escalar sus imágenes es que si tiene muchas de ellas, o si las imágenes de origen son muy grandes, aún puede encontrarse con el mismo problema. +1 a ti Efraín.
Dave
10
A partir de Lollipop, BitmapFactory.Options.inPurgeabley BitmapFactory.Options.inInputShareableestán en desuso developer.android.com/reference/android/graphics/…
Denis Kniazhev
93

Es un error conocido , no se debe a los archivos grandes. Como Android almacena en caché los Drawables, se está quedando sin memoria después de usar pocas imágenes. Pero he encontrado una forma alternativa de hacerlo, omitiendo el sistema de caché predeterminado de Android.

Solución : mueva las imágenes a la carpeta "activos" y use la siguiente función para obtener BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}
Anto Binish Kaspar
fuente
79

Tuve este mismo problema y lo resolví evitando las funciones BitmapFactory.decodeStream o decodeFile y en su lugar utilicé BitmapFactory.decodeFileDescriptor

decodeFileDescriptor parece que llama a métodos nativos diferentes que decodeStream / decodeFile.

De todos modos, lo que funcionó fue esto (tenga en cuenta que agregué algunas opciones como algunas de las anteriores, pero eso no fue lo que marcó la diferencia. Lo que es crítico es la llamada a BitmapFactory.decodeFileDescriptor en lugar de decodeStream o decodeFile ):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

Creo que hay un problema con la función nativa utilizada en decodeStream / decodeFile. He confirmado que se llama a un método nativo diferente cuando se usa decodeFileDescriptor. Además, lo que he leído es "que las imágenes (mapas de bits) no se asignan de una manera estándar de Java sino a través de llamadas nativas; las asignaciones se realizan fuera del montón virtual, ¡pero se cuentan en su contra! "

Fraggle
fuente
1
mismo resultado de la memoria, en realidad no importa qué método lo esté utilizando, depende de la cantidad de bytes que está reteniendo para leer los datos que se quedan sin memoria.
PiyushMishra
72

Creo que la mejor manera de evitarlo OutOfMemoryErrores enfrentarlo y comprenderlo.

Hice una aplicación para causar intencionalmente OutOfMemoryErrory monitorear el uso de memoria.

Después de hacer muchos experimentos con esta aplicación, tengo las siguientes conclusiones:

Voy a hablar sobre las versiones de SDK antes de Honey Comb primero.

  1. El mapa de bits se almacena en el montón nativo, pero se recolectará basura automáticamente, no es necesario llamar a recycle ().

  2. Si {Tamaño de almacenamiento dinámico de VM} + {memoria de almacenamiento dinámico nativa asignada}> = {Límite de tamaño de almacenamiento dinámico de VM para el dispositivo}, y está intentando crear un mapa de bits, se lanzará OOM.

    AVISO: se cuenta el TAMAÑO DE HEAP DE VM en lugar de MEMORIA ASIGNADA DE VM.

  3. El tamaño del montón de VM nunca se reducirá después de crecer, incluso si la memoria de VM asignada se reduce.

  4. Por lo tanto, debe mantener la memoria máxima de la máquina virtual lo más baja posible para evitar que el tamaño de almacenamiento dinámico de la máquina virtual sea demasiado grande para guardar la memoria disponible para mapas de bits.

  5. Llamar manualmente a System.gc () no tiene sentido, el sistema lo llamará primero antes de intentar aumentar el tamaño del almacenamiento dinámico.

  6. El tamaño de almacenamiento dinámico nativo nunca se reducirá también, pero no se cuenta para OOM, por lo que no debe preocuparse.

Entonces, hablemos de SDK Starts de Honey Comb.

  1. El mapa de bits se almacena en el montón de VM, la memoria nativa no se cuenta para OOM.

  2. La condición para OOM es mucho más simple: {Tamaño de almacenamiento dinámico de VM}> = {Límite de tamaño de almacenamiento dinámico de VM para el dispositivo}.

  3. Por lo tanto, tiene más memoria disponible para crear mapas de bits con el mismo límite de tamaño de almacenamiento dinámico, es menos probable que se genere OOM.

Estas son algunas de mis observaciones sobre la recolección de basura y la fuga de memoria.

Puede verlo usted mismo en la aplicación. Si una Actividad ejecutó una AsyncTask que todavía se estaba ejecutando después de que la Actividad fue destruida, la Actividad no obtendrá basura recolectada hasta que finalice AsyncTask.

Esto se debe a que AsyncTask es una instancia de una clase interna anónima, contiene una referencia de la Actividad.

Llamar a AsyncTask.cancel (verdadero) no detendrá la ejecución si la tarea se bloquea en una operación de E / S en el subproceso en segundo plano.

Las devoluciones de llamada son clases internas anónimas también, por lo que si una instancia estática en su proyecto las retiene y no las libera, se perderá memoria.

Si programó una tarea repetitiva o retrasada, por ejemplo, un temporizador, y no llama a cancel () y purge () en onPause (), se perderá memoria.

coocood
fuente
AsyncTask no necesariamente tiene que ser "una instancia de una clase interna anónima", y lo mismo ocurre con Callbackks. Puede crear una nueva clase pública en su propio archivo que extiende AsyncTask, o incluso una private static classen la misma clase. No mantendrán ninguna referencia a la Actividad (a menos que les des una por supuesto)
Simon Forsberg
65

He visto muchas preguntas sobre excepciones OOM y almacenamiento en caché últimamente. La guía del desarrollador tiene un artículo realmente bueno sobre esto, pero algunos tienden a fallar al implementarlo de manera adecuada.

Debido a esto, escribí una aplicación de ejemplo que demuestra el almacenamiento en caché en un entorno Android. Esta implementación aún no ha recibido una OOM.

Mira al final de esta respuesta un enlace al código fuente.

Requisitos:

  • Android API 2.1 o superior (simplemente no pude lograr obtener la memoria disponible para una aplicación en API 1.6, ese es el único fragmento de código que no funciona en API 1.6)
  • Paquete de soporte de Android

Captura de pantalla

caracteristicas:

  • Retiene el caché si hay un cambio de orientación , usando un singleton
  • Use una octava parte de la memoria de la aplicación asignada a la memoria caché (modifique si lo desea)
  • Los mapas de bits grandes se escalan (puede definir los píxeles máximos que desea permitir)
  • Controla que hay una conexión a Internet disponible antes de descargar los mapas de bits
  • Se asegura de que solo está creando instancias de una tarea por fila
  • Si usted está lanzando la ListViewdistancia, simplemente no va a descargar los mapas de bits entre

Esto no incluye:

  • Almacenamiento en caché de disco. Esto debería ser fácil de implementar de todos modos: solo apunte a una tarea diferente que tome los mapas de bits del disco

Código de muestra:

Las imágenes que se descargan son imágenes (75x75) de Flickr. Sin embargo, coloque las URL de imagen que desea procesar y la aplicación lo reducirá si excede el máximo. En esta aplicación, las URL están simplemente en una Stringmatriz.

El LruCachetiene una buena manera de hacer frente a los mapas de bits. Sin embargo, en esta aplicación puse una instancia de una LruCacheclase de caché dentro de otra que creé para que la aplicación sea más factible.

Cosas críticas de Cache.java (el loadBitmap()método es el más importante):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

No debería necesitar editar nada en el archivo Cache.java a menos que desee implementar el almacenamiento en caché de disco.

Cosas críticas de MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView()se llama muy a menudo. Normalmente no es una buena idea descargar imágenes allí si no hemos implementado una verificación que nos asegure que no comenzaremos una cantidad infinita de hilos por fila. Cache.java comprueba si el rowObject.mBitmapUrlya está en una tarea y si lo está, no iniciará otra. Por lo tanto, lo más probable es que no excedamos la restricción de la cola de trabajo del AsyncTaskgrupo.

Descargar:

Puede descargar el código fuente desde https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Ultimas palabras:

He probado esto durante algunas semanas, todavía no he recibido una sola excepción OOM. Probé esto en el emulador, en mi Nexus One y en mi Nexus S. Probé URL de imágenes que contenían imágenes en calidad HD. El único cuello de botella es que lleva más tiempo descargarlo.

Solo hay un escenario posible en el que puedo imaginar que aparecerá la OOM, y es que si descargamos muchas imágenes realmente grandes, y antes de que se escalen y se coloquen en caché, al mismo tiempo ocuparán más memoria y causarán una OOM. Pero esa ni siquiera es una situación ideal de todos modos y lo más probable es que no sea posible resolver de una manera más factible.

¡Informar errores en los comentarios! :-)

revs Wroclai
fuente
43

Hice lo siguiente para tomar la imagen y cambiar su tamaño sobre la marcha. Espero que esto ayude

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Chrispix
fuente
26
Este enfoque escala el mapa de bits. Pero no resuelve el problema de OutOfMemory porque el mapa de bits completo se está decodificando de todos modos.
Fedor
55
Veré si puedo ver mi antiguo código, pero creo que resolvió mis problemas de falta de memoria. Verificará dos veces mi antiguo código.
Chrispix
2
Al menos en este ejemplo, parece que no está manteniendo la referencia al mapa de bits completo, por lo tanto, el ahorro de memoria.
NoBugs
Para mí resolvió el problema de memoria, pero redujo la calidad de las imágenes.
Pamela Sillah
35

Parece que este es un problema de larga duración, con muchas explicaciones diferentes. Tomé el consejo de las dos respuestas presentadas más comunes aquí, pero ninguna de ellas resolvió mis problemas de la VM afirmando que no podía permitirse los bytes para realizar la parte de decodificación del proceso. Después de investigar un poco, aprendí que el verdadero problema aquí es el proceso de decodificación que se lleva lejos del montón NATIVO .

Ver aquí: BitmapFactory OOM me vuelve loco

Eso me llevó a otro hilo de discusión donde encontré un par de soluciones más para este problema. Una es llamar System.gc();manualmente después de que se muestre su imagen. Pero eso realmente hace que su aplicación use MÁS memoria, en un esfuerzo por reducir el montón nativo. La mejor solución a partir del lanzamiento de 2.0 (Donut) es utilizar la opción BitmapFactory "inPurgeable". Entonces simplemente agregué o2.inPurgeable=true;justo después o2.inSampleSize=scale;.

Más sobre este tema aquí: ¿el límite de memoria es solo de 6M?

Ahora, habiendo dicho todo esto, también soy un completo tonto con Java y Android. Entonces, si cree que esta es una forma terrible de resolver este problema, probablemente tenga razón. ;-) Pero esto ha funcionado de maravilla para mí, y ahora me resulta imposible ejecutar la máquina virtual fuera de la memoria caché del montón. El único inconveniente que puedo encontrar es que estás destrozando tu imagen dibujada en caché. Lo que significa que si vuelves a la DERECHA de esa imagen, la estás redibujando cada vez. En el caso de cómo funciona mi aplicación, eso no es realmente un problema. Su experiencia puede ser diferente.

RayHaque
fuente
inPurgeable OOM fijo para mí.
Artem Russakovskii
35

desafortunadamente si Ninguno de los anteriores funciona, entonces Agregue esto a su archivo de Manifiesto . Etiqueta de aplicación interna

 <application
         android:largeHeap="true"
Himanshu Mori
fuente
1
¿Puedes explicar lo que esto realmente hace? Simplemente decirle a la gente que agregue esto no ayuda.
Stealth Rabbi
1
Esta es una muy mala solución. Básicamente no estás tratando de solucionar el problema. En lugar de pedirle al sistema Android que asigne más espacio de almacenamiento dinámico para su aplicación. Esto tendrá implicaciones muy malas en su aplicación, ya que su aplicación consume mucha batería, ya que el GC debe ejecutar un gran espacio de almacenamiento dinámico para limpiar la memoria y también el rendimiento de su aplicación será más lento.
Prakash
2
entonces, ¿por qué Android nos permite agregar este android: largeHeap = "true" en nuestro manifiesto? Ahora estás desafiando a Android.
Himanshu Mori
32

Use esto bitmap.recycle();Esto ayuda sin ningún problema de calidad de imagen.

Arsalan
fuente
99
Según la API, no es necesario llamar a recycle ().
Artem Russakovskii
28

Tengo una solución mucho más efectiva que no necesita escala de ningún tipo. Simplemente decodifique su mapa de bits solo una vez y luego almacénelo en un mapa con su nombre. Luego, simplemente recupere el mapa de bits con el nombre y configúrelo en ImageView. No hay nada más que deba hacerse.

Esto funcionará porque los datos binarios reales del mapa de bits decodificado no se almacenan en el montón de VM dalvik. Se almacena externamente. Por lo tanto, cada vez que decodifica un mapa de bits, asigna memoria fuera del montón de VM que nunca es reclamada por GC

Para ayudarlo a apreciar mejor esto, imagine que ha guardado su imagen en la carpeta dibujable. Simplemente obtienes la imagen haciendo getResources (). GetDrwable (R.drawable.). Esto NO decodificará su imagen cada vez, sino que reutilizará una instancia ya decodificada cada vez que la llame. Entonces, en esencia, está en caché.

Ahora, dado que su imagen está en un archivo en algún lugar (o incluso puede provenir de un servidor externo), es SU responsabilidad almacenar en caché la instancia de mapa de bits decodificada para reutilizarla donde sea necesario.

Espero que esto ayude.

Parth Mehta
fuente
44
"y luego almacenarlo en caché en un mapa con su nombre". ¿Cómo guardas exactamente tus imágenes?
Vincent
3
¿Realmente has intentado esto? Aunque los datos de píxeles no se almacenan realmente en el montón de Dalvik, su tamaño en la memoria nativa se informa a la máquina virtual y se cuenta contra su memoria disponible.
ErikR
3
@ Vincent Creo que no es difícil almacenarlos en un Mapa. Sugeriría algo como el mapa HashMap <KEY, Bitmap>, donde la clave puede ser una cadena de la fuente o cualquier cosa que tenga sentido para usted. Supongamos que toma un camino como CLAVE, lo almacena como map.put (Path, Bitmap) y lo recibe a través de map.get (Path)
Rafael T
3
es probable que desee utilizar HashMap <String, SoftReference <Bitmap>> si está implementando una caché de imágenes; de lo contrario, puede quedarse sin memoria de todos modos; tampoco creo que "asigne memoria fuera del montón VM que nunca es reclamada por GC "es cierto, la memoria se recupera, según tengo entendido, puede ser un retraso, que es para lo que sirve bitmap.recycle (), como una pista para reclamar la memoria temprano ...
Dori
28

He resuelto el mismo problema de la siguiente manera.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);
Prerna
fuente
eso está bien, pero estoy usando múltiples mapas de bits para dibujar un círculo en OnCreate y llamar a la actividad 4-5 veces, así que cómo borrar el mapa de bits y cómo eliminar el mapa de bits y crear nuevamente por segunda vez cuando la actividad 0nCreate ..
ckpatel
27

Hay dos problemas aquí....

  • La memoria de mapa de bits no está en el montón de VM sino en el montón nativo: vea BitmapFactory OOM volviéndome loco
  • La recolección de basura para el montón nativo es más vaga que el montón VM, por lo que debe ser bastante agresivo al hacer bitmap.recycle y bitmap = null cada vez que pasa por una actividad onPause o onDestroy
Torid
fuente
Está en el montón VM desde Android
2.3+
27

¡Esto funcionó para mí!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}
Luke Taylor
fuente
20

Ninguna de las respuestas anteriores funcionó para mí, pero se me ocurrió una solución horriblemente fea que resolvió el problema. Agregué una imagen muy pequeña de 1x1 píxeles a mi proyecto como recurso, y la cargué en mi ImageView antes de llamar a la recolección de basura. Creo que podría ser que ImageView no lanzara el mapa de bits, por lo que GC nunca lo recogió. Es feo, pero parece estar funcionando por ahora.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
Miguel
fuente
parece que imageView realmente no recicla el mapa de bits por sí mismo. Me ayudó, gracias
Dmitry Zaytsev
@ Mike puedes agregar el código completo del cargador de imágenes o puedes darme el enlace para cargar imágenes de mapa de bits. Si uso reciclar en mapa de bits, se muestra toda mi vista de lista, pero todos los elementos se muestran en blanco
TNR
@ Mike, ¿puedes decir si hago imageView = nulo antes de llamar a la recolección de basura, es bueno o no?
Youddh
@TNR Creo que lo que te estás perdiendo aquí es que bitmapen el código anterior está la imagen ya mostrada anteriormente, debes reciclar eso, borrar cualquier referencia a él, imageViewolvidarlo también configurando un pequeño reemplazo gc(); y después de todo esto: cargue su NUEVA imagen bitmapy muéstrela ...en el código anterior.
TWiStErRob
Esto está mal. Siempre debe borrar su contenido de imageView ANTES de reciclar el mapa de bits (en lugar de hacerlo mientras se muestra y se usa).
FindOut_Quran
20

Grandes respuestas aquí, pero quería una clase completamente utilizable para abordar este problema ... así que hice una.

Aquí está mi clase BitmapHelper que es a prueba de OutOfMemoryError :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}
Pascal
fuente
Para cualquiera que use esto: acabo de corregir un error: "int scaledBitmapHeight = scaledBitmap.getWidth ();" está mal (obviamente. Lo reemplacé con "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal
19

Esto funciona para mi.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

y esto está en C # monodroid. puedes cambiar fácilmente la ruta de la imagen. Lo importante aquí son las opciones a configurar.

Dobermaxx99
fuente
16

Este parece ser el lugar apropiado para compartir mi clase de utilidad para cargar y procesar imágenes con la comunidad, puede usarlo y modificarlo libremente.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * 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;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}
Emil Davtyan
fuente
16

En una de mis aplicaciones, necesito tomar una foto Camera/Gallery. Si el usuario hace clic en la imagen de la cámara (puede ser 2MP, 5MP u 8MP), el tamaño de la imagen varía de kBsa MBs. Si el tamaño de la imagen es menor (o hasta 1-2 MB) por encima del código funciona bien, pero si tengo una imagen de tamaño superior a 4 MB o 5 MB, entonces OOMaparece en el cuadro :(

luego he trabajado para resolver este problema y finalmente he realizado la siguiente mejora en el código de Fedor (All Credit to Fedor por hacer una solución tan agradable) :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the
                                    // future

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

¡Espero que esto ayude a los amigos que enfrentan el mismo problema!

Para más información, consulte esto

Rupesh Yadav
fuente
14

Me encontré con este problema hace un par de minutos. Lo resolví haciendo un mejor trabajo en la administración de mi adaptador listview. Pensé que era un problema con los cientos de imágenes de 50x50px que estaba usando, resultó que estaba tratando de inflar mi vista personalizada cada vez que se mostraba la fila. Simplemente probando para ver si la fila se había inflado, eliminé este error y estoy usando cientos de mapas de bits. Esto es en realidad para un Spinner, pero el adaptador base funciona igual para un ListView. Esta solución simple también mejoró en gran medida el rendimiento del adaptador.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...
BajaBob
fuente
3
No puedo agradecerles lo suficiente por esto! Estaba persiguiendo el problema equivocado antes de ver esto. Sin embargo, la pregunta para usted: dado que cada una de mis filas de la lista tiene un nombre y una foto únicos, tuve que usar una matriz convertView para retener cada uno de los valores de las filas. No podía ver cómo usar una sola variable te permitiría hacerlo. ¿Me estoy perdiendo de algo?
PeteH
13

He pasado todo el día probando estas soluciones y lo único que funcionó para mí es los enfoques anteriores para obtener la imagen y llamar manualmente al GC, que sé que no se supone que sea necesario, pero es lo único que funcionó cuando pongo mi aplicación bajo pruebas de carga pesada cambiando entre actividades. Mi aplicación tiene una lista de imágenes en miniatura en una vista de lista en (digamos actividad A) y cuando hace clic en una de esas imágenes lo lleva a otra actividad (digamos actividad B) que muestra una imagen principal para ese elemento. Cuando cambiaba entre las dos actividades, eventualmente recibía el error OOM y la aplicación se cerraba.

Cuando llegaba a la mitad de la vista de lista, se bloqueaba.

Ahora, cuando implemente lo siguiente en la actividad B, puedo revisar toda la vista de lista sin ningún problema y seguir y seguir y seguir ... y es bastante rápido.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
Jesse
fuente
¡Amo tu solución! También pasé horas resolviendo este error, ¡tan molesto! Editar: Lamentablemente, el problema aún existe cuando cambio la orientación de mi pantalla en modo horizontal ...
Xarialon
Esto finalmente me ayudó junto con: - Opciones de BitmapFactory.Options = new BitmapFactory.Options (); opciones.InPurgeable = verdadero; opciones.InSampleSize = 2;
user3833732
13

Este problema solo ocurre en los emuladores de Android. También enfrenté este problema en un emulador, pero cuando registré un dispositivo, funcionó bien.

Así que por favor verifique un dispositivo. Se puede ejecutar en el dispositivo.

hackjutsu
fuente
12

Mis 2 centavos: resolví mis errores de OOM con mapas de bits de la siguiente manera:

a) escalar mis imágenes por un factor de 2

b) usar la biblioteca Picasso en mi Adaptador personalizado para ListView, con una llamada en getView como esta:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

matsoftware
fuente
Me alegra que hayas mencionado a Picasso, porque facilita la carga de imágenes. Especialmente almacenados remotamente.
Chrispix
12

use este código para cada imagen en select de SdCard o dibujable para convertir objetos de mapa de bits.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

use la ruta de su imagen en ImageData_Path.get (img_pos) .getPath () .

Gaurav Pansheriya
fuente
12

En general, el tamaño de almacenamiento dinámico del dispositivo Android es de solo 16 MB (varía según el dispositivo / sistema operativo, vea los tamaños de almacenamiento dinámico posteriores ), si está cargando las imágenes y cruza el tamaño de 16 MB, se saldrá de la memoria, en lugar de usar el mapa de bits para cargar las imágenes de la tarjeta SD o de los recursos o incluso de la red intentan usar getImageUri , cargar el mapa de bits requiere más memoria, o puede establecer el mapa de bits en nulo si su trabajo se realizó con ese mapa de bits.

Rohit Sharma
fuente
1
Y si setImageURI sigue recibiendo una excepción, consulte este stackoverflow.com/questions/15377186/…
Mahesh
11

Todas las soluciones aquí requieren establecer un IMAGE_MAX_SIZE. Esto limita los dispositivos con hardware más potente y si el tamaño de la imagen es demasiado bajo, se ve feo en la pantalla HD.

Salí con una solución que funciona con mi Samsung Galaxy S3 y varios otros dispositivos, incluidos los menos potentes, con una mejor calidad de imagen cuando se usa un dispositivo más potente.

La esencia de esto es calcular la memoria máxima asignada para la aplicación en un dispositivo en particular, luego configurar la escala para que sea lo más baja posible sin exceder esta memoria. Aquí está el código:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Establecí la memoria máxima utilizada por este mapa de bits como el 25% de la memoria máxima asignada, es posible que deba ajustar esto a sus necesidades y asegurarse de que este mapa de bits esté limpio y no permanezca en la memoria cuando haya terminado de usarlo. Por lo general, uso este código para realizar la rotación de la imagen (mapa de bits de origen y de destino), por lo que mi aplicación necesita cargar 2 mapas de bits en la memoria al mismo tiempo, y el 25% me da un buen búfer sin quedarse sin memoria al realizar la rotación de la imagen.

Espero que esto ayude a alguien por ahí ...

Bruce
fuente
11

Esto OutofMemoryExceptionno puede resolverse totalmente llamando al System.gc()y así sucesivamente.

Al referirse al ciclo de vida de la actividad

Los estados de actividad están determinados por el propio sistema operativo sujeto al uso de memoria para cada proceso y la prioridad de cada proceso.

Puede considerar el tamaño y la resolución de cada una de las imágenes de mapa de bits utilizadas. Recomiendo reducir el tamaño, volver a muestrear a una resolución más baja, consultar el diseño de las galerías (una imagen pequeña PNG y una imagen original).

Raju Gujarati
fuente
11

Este código ayudará a cargar mapas de bits grandes desde dibujables

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
viñedo
fuente
Bien, ¿crees que sería mejor usar un cargador en lugar de una tarea asincrónica?
Chrispix
¿Qué tal Bitmap.Config.ARGB_565? Si la alta calidad no es crítica.
Hamzeh Soboh