Pregunta Extraño problema de memoria al cargar una imagen en un objeto Bitmap


Tengo una vista de lista con un par de botones de imagen en cada fila. Cuando hace clic en la fila de la lista, 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 fuera de la tarjeta SD), la aplicación regresa de la actividad a la listview actividad al manejador de resultados para reiniciar 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 píxel como el src para el botón de imagen sobre la marcha. Así que cambié el tamaño de la imagen que salió de la cámara del teléfono.

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

  • ¿Hay alguna manera de construir el adaptador de lista fácilmente fila por fila, donde puedo cambiar el tamaño sobre la marcha (Poco sabio)?

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 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 muestra sería agradable.

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

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

Dónde 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

1105


origen


Respuestas:


los Entrenamiento de Android clase, "Mostrar mapas de bits de manera eficiente", ofrece buena información para comprender y tratar la excepción java.lang.OutOfMemoryError: bitmap size exceeds VM budget al cargar mapas de bits


Leer las dimensiones y el tipo de mapa de bits

los BitmapFactory clase proporciona varios métodos de decodificación (decodeByteArray(), decodeFile(), decodeResource(), etc.) para crear un Bitmap de varias fuentes. Elija el método de decodificación más apropiado según su fuente de datos de imágenes. Estos métodos intentan asignar memoria para el mapa de bits construido y, por lo tanto, pueden dar como resultado fácilmente un OutOfMemory excepción. Cada tipo de método de decodificación tiene firmas adicionales que le permiten especificar opciones de decodificación a través del BitmapFactory.Options clase. Configurando el inJustDecodeBounds propiedad a true mientras que la decodificación evita la asignación de memoria, volviendo null para el objeto de mapa de bits, pero la configuración 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 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.OutOfMemory excepciones, compruebe las dimensiones de un mapa de bits antes de decodificarlo, a menos que confíe plenamente en que la fuente le proporcione datos de imágenes de tamaño predecible que se ajusten cómodamente a la memoria disponible.


Cargue una versión reducida en la memoria

Ahora que las dimensiones de la imagen son conocidas, 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 estimado de la memoria al cargar la imagen completa en la memoria.
  • La cantidad de memoria que está dispuesto a cargar 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 va a 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 una ImageView.

Para decirle al decodificador que muestre por debajo la imagen, cargue una versión más pequeña en la memoria, establezca inSampleSize a true en tus BitmapFactory.Options objeto. 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 utiliza 0.75MB en lugar de 12MB 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 basada en el 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 usa un   valor final al redondear a la potencia más cercana de dos, según el    inSampleSize documentación.

Para usar este método, primero decodifique con inJustDecodeBounds ajustado a true, pasar las opciones y luego decodificar de nuevo con el nuevo inSampleSize valor y inJustDecodeBounds ajustado a 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 hace que sea fácil cargar 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 apropiado BitmapFactory.decode* método según sea necesario.


558



Para solucionar el error de OutOfMemory, debe hacer algo como esto:

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

Esta inSampleSize la opció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í. Entonces encuentra lo mejor inSampleSize valor, 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;
}

856



He hecho una pequeña mejora en el código de Fedor. Básicamente hace lo mismo, pero sin (en mi opinión) feo while loop 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;
}

352



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 un tamaño razonable. De todos modos, aquí están los dos cambios que arreglaron mi problema (y hicieron que mi aplicación fuera muy receptiva).

1) Cada vez que lo haces BitmapFactory.decodeXYZ(), asegúrese de pasar un BitmapFactory.Options con inPurgeable ajustado a true (y preferiblemente con inInputShareable también configurado para true)

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888). ¡Quiero decir NUNCA! Nunca tuve esa cosa que no aumentara el error de memoria después de algunos pases. Ninguna cantidad de recycle(), System.gc()lo que sea que haya ayudado Siempre planteó una excepción. La otra forma en que realmente funciona es tener una imagen ficticia en tus dibujos (u otro mapa de bits que decodificaste usando el paso 1 anterior), cambiarla a lo que quieras y luego manipular el mapa de bits resultante (como pasarlo a un lienzo para más diversión). Entonces, lo que deberías usar es: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Si por alguna razón usted DEBE usar el método de creación de fuerza bruta, entonces al menos pase Config.ARGB_4444.

Esto casi garantiza que le ahorrará horas si no días. Todo lo que se dice sobre escalar la imagen, etc., realmente no funciona (a menos que considere la posibilidad de obtener una solución de tamaño incorrecto o degradada).


220



Es un error conocido, no es por archivos grandes Desde Android Cache los Drawables, se está quedando sin memoria después de usar algunas imágenes. Pero encontré una forma alternativa de hacerlo, omitiendo el sistema de caché predeterminado de Android.

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

84



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 el decodeStream / decodeFile.

De todos modos, lo que funcionó fue esto (nótese que agregué algunas opciones como algunas de las anteriores, pero eso no fue lo que marcó la diferencia. Lo que es fundamental 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. También lo que he leído es "que las imágenes (mapas de bits) no están asignadas de una manera estándar de Java sino a través de llamadas nativas, las asignaciones se hacen fuera del montón virtual, pero contado en contra!"


69



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

Hice una aplicación intencionalmente causar OutOfMemoryErrory monitorear el uso de la 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 obtendrá basura recogida automáticamente, llamando a reciclar () es innecesario.

  2. Si {Tamaño de almacenamiento dinámico de VM} + {memoria de almacenamiento dinámico 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: VM HEAP SIZE se cuenta en lugar de VM ALLOCATED MEMORY.

  3. El tamaño del almacenamiento dinámico de VM nunca se reducirá una vez crecido, incluso si la memoria de VM asignada se reduce.

  4. Por lo tanto, debe mantener la memoria de VM máxima lo más baja posible para evitar que el tamaño del almacenamiento dinámico de VM crezca demasiado 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 hacer crecer el tamaño del montón.

  6. El tamaño de Heap nativo nunca se reducirá, pero no cuenta para OOM, por lo que no debe preocuparse por ello.

Entonces, hablemos de los inicios de SDK 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: {VM heap size}> = {VM heap límite de tamaño para el dispositivo}.

  3. Para que tenga 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.

Aquí están algunas de mis observaciones acerca de Garbage Collection y Memory Leak.

Puedes verlo tú mismo en la aplicación. Si una Actividad ejecutó una AsyncTask que aún se estaba ejecutando después de que se destruyó la Actividad, la Actividad no obtendrá basura recolectada hasta que la AsyncTask finalice.

Esto es porque 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 está bloqueada en una operación IO en el hilo de fondo.

Las rellamadas también son clases internas anónimas, de modo que si una instancia estática en su proyecto las retiene y no las libera, la memoria se filtraría.

Si programó una tarea repetitiva o retrasada, por ejemplo, un temporizador, y no llama a cancelar () y purgar () en OnPause (), la memoria se filtró.


64



Recientemente he visto muchas preguntas sobre las excepciones y el almacenamiento en caché de OOM. La guía del desarrollador 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 recibido un OOM.

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

Requisitos:

  • Android API 2.1 o superior (simplemente no pude conseguir la memoria disponible para una aplicación en la API 1.6, que es la única pieza de código que no funciona en la API 1.6)
  • Paquete de soporte de Android

Screenshot

caracteristicas:

  • Retiene la caché si hay un cambio de orientación, usando un singleton
  • Utilizar uno ocho de la memoria de la aplicación asignada al caché (modifique si lo desea)
  • Grandes mapas de bits se escala (puede definir los píxeles máximos que desea permitir)
  • Controles que hay una conexión a internet disponible antes de descargar los bitmaps
  • Asegúrate de estar solo instanciando una tarea por fila
  • Si estás arrojando el ListView de distancia, simplemente no 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, simplemente apunta a una tarea diferente que toma 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, coloque las urls de imagen que desee que se procesen, y la aplicación la reducirá si excede el máximo. En esta aplicación, las URL están simplemente en una Stringformación.

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

Las 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.mBitmapUrl ya está en una tarea y si lo es, no comenzará otra. Por lo tanto, lo más probable es que no excedamos la restricción de cola de trabajo de la AsyncTask piscina.

Descargar:

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


Ultimas palabras:

He probado esto durante unas semanas, todavía no he recibido una sola excepción de OOM. Lo he probado en el emulador, en mi Nexus One y en mi Nexus S. He probado urls de imágenes que contienen imágenes que están en calidad HD. El único cuello de botella es que lleva más tiempo descargar.

Solo hay un escenario posible en el que puedo imaginar que aparecerá la OOM, y eso es si descargamos muchas imágenes realmente grandes, y antes de que se escalen y pongan en la caché, tomarán simultáneamente más memoria y causarán una OOM. Pero eso ni siquiera es una situación ideal de todos modos y lo más probable es que no sea posible resolverla de una manera más factible.

¡Informe errores en los comentarios! :-)


58



Hice lo siguiente para tomar la imagen y cambiar el 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);    

36