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




galeria studio (25)

Tengo una vista de lista con un par de botones de imagen en cada fila. Al hacer clic en la fila de la lista, se inicia una nueva actividad. He tenido que crear mis propias pestañas debido a un problema con el diseño de la cámara. La actividad que se lanza 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 regresa de la actividad a la vista de lista y 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 está haciendo 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 es píxel como la src del botón de imagen sobre la marcha. Así que solo redimensioné la imagen que salió de la cámara del teléfono.

El problema es que me sale un error de falta de memoria cuando intenta volver y volver a iniciar la 2ª actividad.

  • ¿Hay alguna manera de construir el adaptador de lista fila por fila, donde pueda cambiar el tamaño sobre la marcha ( poco a poco )?

Esto sería preferible ya que también necesito realizar algunos cambios en las propiedades de los widgets / elementos en cada fila, ya que no puedo seleccionar una fila con pantalla táctil debido a un problema de enfoque. ( Puedo usar la bola de 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 un código de ejemplo para eso sería bueno.

Tan pronto como inhabilité la imagen en la vista de lista funcionó bien otra vez.

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.imagefilename es un 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

Answers

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

decodeFileDescriptor parece que llama a diferentes métodos nativos 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 es lo que hizo 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 al usar decodeFileDescriptor. Además, lo que he leído es "que las Imágenes (mapas de bits) no se asignan de forma estándar en Java, sino a través de llamadas nativas; las asignaciones se realizan fuera del montón virtual, ¡pero se cuentan en su contra! "


Este parece ser el lugar adecuado para compartir mi clase de utilidad para cargar y procesar imágenes con la comunidad. Le invitamos a usarla y modificarla 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://.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;
    }
}

Este código ayudará a cargar mapa de bits grande desde dibujable

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

Vengo de la experiencia de iOS y me frustraba 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 fuera muy receptiva).

1) Cada vez que haga BitmapFactory.decodeXYZ() , asegúrese de pasar un BitmapFactory.Options con inPurgeable establecido en true (y preferiblemente con inInputShareable también establecido en true ).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888) . Me refiero a NUNCA! Nunca he tenido esa cosa que no levante error de memoria después de pocas pasadas. No hay cantidad de recycle() , System.gc() , lo que haya ayudado. Siempre planteaba la excepción. La otra forma en que realmente funciona es tener una imagen ficticia en tus dibujables (u otro mapa de bits que decodificaste usando el paso 1 anterior), cambiar la escala a lo que quieras, luego manipular el mapa de bits resultante (como pasarlo a un lienzo). para más diversión). Entonces, lo que debería usar en su lugar es: Bitmap.createScaledBitmap(srcBitmap, width, height, false) . Si, por alguna razón, DEBE utilizar el método de creación de fuerza bruta, al menos apruebe Config.ARGB_4444 .

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


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 que me vuelve loco
  • La recolección de basura para el montón nativo es más perezosa que el montón de la máquina virtual, por lo que debe ser bastante agresivo al hacer bitmap.recycle y bitmap = null cada vez que realice una actividad en OnPause o onDestroy

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

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


Todas las soluciones aquí requieren configurar 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.

Se me ocurrió 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 utiliza un dispositivo más potente.

Lo esencial es calcular la memoria máxima asignada para la aplicación en un dispositivo en particular y luego configurar la escala a la 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;
}

Configuro que la memoria máxima utilizada por este mapa de bits sea el 25% de la memoria máxima asignada, es posible que necesite 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. Normalmente, uso este código para realizar la rotación de la imagen (mapa de bits de origen y 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í ..


La clase de capacitación de Android , " Visualización eficiente de java.lang.OutOfMemoryError: bitmap size exceeds VM budget bits ", ofrece información excelente para comprender y manejar la excepción java.lang.OutOfMemoryError: bitmap size exceeds VM budget cuando se cargan java.lang.OutOfMemoryError: bitmap size exceeds VM budget bits.

Leer dimensiones y tipo de mapa de bits

La clase BitmapFactory proporciona varios métodos de decodificación ( decodeByteArray() , decodeFile() , decodeResource() , etc.) para crear un Bitmap desde varias fuentes. Elija el método de decodificación más adecuado en función de 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 excepción OutOfMemory . Cada tipo de método de decodificación tiene firmas adicionales que le permiten especificar opciones de decodificación a través de la clase BitmapFactory.Options . Establecer la propiedad inJustDecodeBounds en true mientras se decodifica evita la asignación de memoria, devolviendo null para el objeto de mapa de bits pero configurando outWidth , outHeight y outMimeType . Esta técnica le permite leer las dimensiones y el tipo de los datos de imagen antes de la construcción (y 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 las excepciones de java.lang.OutOfMemory , 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 adapten cómodamente a la memoria disponible.

Cargar 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 de submuestreo. Aquí hay algunos factores a considerar:

  • Uso estimado de la memoria para cargar la imagen completa en la memoria.
  • La cantidad de memoria que está dispuesto a comprometerse a 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 finalmente se mostrará en una miniatura de 128x96 píxeles en un ImageView .

Para decirle al decodificador que muestree la imagen en una submuestra, cargando una versión más pequeña en la memoria, establezca inSampleSize en true en su objeto BitmapFactory.Options . Por ejemplo, una imagen con resolución 2048x1536 que se decodifica con un inSampleSize de 4 produce un mapa de bits de aproximadamente 512x384. Cargar esto en la memoria usa 0.75MB en lugar de 12MB para la imagen completa (asumiendo 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 de un ancho y una altura de destino:

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 al redondear a la potencia más cercana de dos, según la documentación de inSampleSize .

Para usar este método, primero decodifique con inJustDecodeBounds establecido en true , pase las opciones y luego inSampleSize nuevamente usando el nuevo valor inJustDecodeBounds e inJustDecodeBounds establecido 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 ImageView que 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 método apropiado BitmapFactory.decode* según sea necesario.


He hecho una pequeña mejora al código de Fedor. Básicamente, hace lo mismo, pero sin el (aunque en mi opinión) el bucle mientras está feo y siempre da como resultado una potencia de dos. Felicitaciones a Fedor por hacer la solución original, me quedé atascado hasta que encontré la suya, y luego pude hacer ésta :)

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

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

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

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


desafortunadamente, si ninguno de los anteriores funciona, agregue esto a su archivo de manifiesto . Etiqueta de aplicación interior

 <application
         android:largeHeap="true"

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

Aquí está mi clase de BitmapHelper que es la prueba 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 [email protected]
            // 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;
    }

}

Tengo una solución mucho más efectiva que no necesita ningún tipo de escalado. Simplemente decodifique su mapa de bits solo una vez y luego guárdelo 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 hacer.

Esto funcionará porque los datos binarios reales del mapa de bits descodificado no se almacenan dentro del 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 GC nunca reclama.

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

Ahora 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 descodificada para reutilizarla en cualquier lugar que sea necesario.

Espero que esto ayude.


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

Solución : Mueva las imágenes a la carpeta "elementos" 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);
}

Por lo general, el tamaño de la pila del dispositivo Android es de solo 16 MB (varía según el dispositivo / SO ver los tamaños de pila posteriores ), si está cargando las imágenes y cruza el tamaño de 16 MB, se eliminará la excepción de memoria, en lugar de usar el mapa de bits para la carga. las imágenes de la tarjeta SD o de los recursos, o incluso de la red, intentan usar getImageUri , la carga del mapa de bits requiere más memoria, o puede establecer el mapa de bits en nulo si realiza su trabajo con ese mapa de bits.


He pasado todo el día probando estas soluciones y lo único que funcionó para mí son los enfoques anteriores para obtener la imagen y llamar manualmente al GC, lo que sé que no es necesario, pero es lo único que funcionó. cuando pongo mi aplicación en 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 alternaba entre las dos actividades, al final me salía el error de OOM y la aplicación se cerraba.

Cuando llegaba a la mitad de la lista, se caía.

Ahora, cuando implemento lo siguiente en la actividad B, puedo recorrer 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();  
}

He visto muchas preguntas sobre las excepciones de OOM y el almacenamiento en caché últimamente. La guía para desarrolladores tiene un artículo realmente bueno sobre esto, pero algunos tienden a fallar al implementarlo de una 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 obtenido un OOM.

Mire el final de esta respuesta para obtener un enlace al código fuente.

Requisitos:

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

caracteristicas:

  • Retiene el caché si hay un cambio de orientación , usando un singleton
  • Use un octavo de la memoria de la aplicación asignada al caché (modifíquelo si lo desea)
  • Los mapas de bits grandes se escalan (puede definir los píxeles máximos que desea permitir)
  • Controla que haya una conexión a internet disponible antes de descargar los mapas de bits.
  • Se asegura de que solo esté creando una tarea por fila
  • Si está arrojando el ListView distancia, simplemente no descargará los mapas de bits entre

Esto no incluye:

  • Almacenamiento en caché de disco. De todos modos, esto debería ser fácil de implementar: solo señale una tarea diferente que tome los mapas de bits del disco

Código de muestra:

Las imágenes que se están descargando son imágenes (75x75) de Flickr. Sin embargo, coloca las urls de imágenes que quieras procesar y la aplicación las reducirá si excede el máximo. En esta aplicación, las direcciones URL están simplemente en una matriz de String .

El LruCache tiene una buena manera de lidiar con mapas de bits. Sin embargo, en esta aplicación coloco una instancia de un LruCache dentro de otra clase de caché que creé para que la aplicación sea más factible.

Las cosas críticas de loadBitmap() método loadBitmap() 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 tener que editar nada en el archivo Cache.java a menos que desee implementar el almacenamiento en caché del 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 iniciaremos una cantidad infinita de hilos por fila. Cache.java verifica 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 de la AsyncTaskagrupación.

Descargar:

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

Ultimas palabras:

He probado esto por algunas semanas, aún no he recibido una sola excepción OOM. He probado esto en el emulador, en mi Nexus One y en mi Nexus S. He probado urls de imágenes que contienen imágenes en calidad HD. El único cuello de botella es que la descarga lleva más tiempo.

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 el caché, ocuparán simultáneamente más memoria y causarán una OOM. Pero de todos modos, ni siquiera es una situación ideal y es muy probable que no sea posible resolverla de una manera más viable.

Reportar errores en los comentarios! :-)


Creo que la mejor manera de evitar el OutOfMemoryError es enfrentarlo y entenderlo.

Hice una app para causar intencionalmente OutOfMemoryError y monitorear el uso de la memoria.

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

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

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

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

    AVISO: VM HEAP SIZE se cuenta en lugar de VM ALLOCATED MEMORY.

  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 el pico de memoria VM lo más bajo posible para evitar que VM Heap Size crezca demasiado para guardar la memoria disponible para los mapas de bits.

  5. Manualmente llamar a System.gc () no tiene sentido, el sistema lo llamará primero antes de intentar aumentar el tamaño del montón.

  6. Native Heap Size nunca se reducirá, pero no cuenta para OOM, por lo que no hay que preocuparse por eso.

Entonces, vamos a hablar 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 pila de VM}> = {Límite de tamaño de pila 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 pila, es menos probable que se genere OOM.

Estas son algunas de mis observaciones sobre la recolección de basura y la pérdida de memoria.

Puedes verlo tú mismo en la aplicación. Si una Actividad ejecutó una AsyncTask que aún se estaba ejecutando después de que la Actividad fue destruida, la Actividad no se recogerá la basura hasta que finalice la 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 (true) no detendrá la ejecución si la tarea está bloqueada en una operación de IO en el subproceso en segundo plano.

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

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


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 ninguno de estos resolvió mis problemas de la VM alegando que no podía pagar los bytes para realizar la parte de decodificación del proceso. Después de algunas excavaciones, aprendí que el verdadero problema aquí es el proceso de decodificación que se aleja del montón NATIVE .

Vea aquí: BitmapFactory OOM me está volviendo 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 usar la opción BitmapFactory "inPurgeable". Así que simplemente agregué o2.inPurgeable=true;justo despuéso2.inSampleSize=scale; .

Más sobre ese tema aquí: ¿Es el límite de almacenamiento de memoria solo 6M?

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


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


Acabo de encontrarme 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, resulta que estaba intentando inflar mi vista personalizada cada vez que se mostraba la fila. Simplemente haciendo pruebas 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 de la misma manera 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);
    }
...

Esto OutofMemoryExceptionno se puede resolver 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).


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());
    }
}

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);

¡He encontrado una solución muy fácil! Si tiene un formulario y desea tener un botón de envío personalizado, puede usar un código como este:

<button type="submit">
    <img src="login.png" onmouseover="this.src='login2.png';" onmouseout="this.src='login.png';" />
</button>

O simplemente dirígelo a un enlace de una página.





android image bitmap out-of-memory android-bitmap