Manejo de grandes mapas de bits

Tengo un montón de URL de imagen. Tengo que descargar estas imágenes y mostrarlas en mi aplicación una a una. Estoy guardando las imágenes en una Colección usando SoftReferences y también en Sdcard para evitar retags y mejorar la experiencia del usuario.

El problema es que no sé nada sobre el tamaño de los mapas de bits. Y resulta que estoy obteniendo OutOfMemoryExceptions esporádicamente, cuando estoy usando el BitmapFactory.decodeStream(InputStream) . Por lo tanto, elegí reducir la resolución de las imágenes utilizando las opciones de BitmapFactory (tamaño de muestra = 2). Esto dio un mejor resultado: sin OOM, pero esto afecta la calidad de las imágenes más pequeñas.

¿Cómo debo manejar estos casos? ¿Hay alguna manera de disminuir la resolución de imágenes de alta resolución?

Hay una opción en la clase BitmapFactory.Options (una que he pasado por alto) llamada en inJustDecodeBounds , javadoc cuyo dice:

Si se establece en verdadero, el decodificador devolverá nulo (sin bitmap), pero los campos de salida todavía se establecerán, lo que permite que el llamante consulte el bitmap sin tener que asignar la memoria a sus píxeles.

Lo utilicé para descubrir el tamaño real del bitmap y luego inSampleSize usando la opción de tamaño de muestra. Esto al menos evita cualquier error OOM mientras decodifica el archivo.

Referencia:
1. Manejo de mapas de bits más grandes
2. ¿Cómo obtengo información de bitmap antes de decodificar?

Lo que yo mismo he hecho es:

  • usar en inJustDecodeBounds para obtener el tamaño original de la imagen
  • tener una superficie máxima fija para cargar el bitmap (digamos 1Mpixels)
  • compruebe si la superficie de la imagen está por debajo del límite, en caso afirmativo, luego cárguelo directamente
  • si no calcula el ancho y la altura ideales en los que debe cargar el bitmap para mantenerse por debajo de la superficie máxima (aquí hay algunas matemáticas simples que hacer). Esto le da una proporción de flotación que desea aplicar al cargar el bitmap
  • ahora desea traducir esta relación a un inSampleSize adecuado (una potencia de 2 que no degrada la calidad de la imagen). Yo uso esta función:
 int k = Integer.highestOneBit ((int) Math.floor (ratio));
 si (k == 0) devuelve 1;
 else return k;
  • luego, debido a que el bitmap se habrá cargado con una resolución ligeramente mayor que la superficie máxima (porque debe usar una potencia menor de 2), tendrá que cambiar el tamaño del bitmap, pero será mucho más rápido.

Después de unos días luchando para evitar todos los errores OutOfMemory que estaba obteniendo con diferentes dispositivos, creo esto:

 private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) { Bitmap bitmap = null; try { BitmapFactory.Options outDimens = getBitmapDimensions(uri); int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight); bitmap = downsampleBitmap(uri, sampleSize); } catch (Exception e) { //handle the exception(s) } return bitmap; } private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException { BitmapFactory.Options outDimens = new BitmapFactory.Options(); outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap) InputStream is= getContentResolver().openInputStream(uri); // if Options requested only the size will be returned BitmapFactory.decodeStream(is, null, outDimens); is.close(); return outDimens; } private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) { int inSampleSize = 1; if (height > targetHeight || width > targetWidth) { // Calculate ratios of height and width to requested height and // width final int heightRatio = Math.round((float) height / (float) targetHeight); final int widthRatio = Math.round((float) width / (float) targetWidth); // 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; } private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException { Bitmap resizedBitmap; BitmapFactory.Options outBitmap = new BitmapFactory.Options(); outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap outBitmap.inSampleSize = sampleSize; InputStream is = getContentResolver().openInputStream(uri); resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap); is.close(); return resizedBitmap; } 

Este método funciona con todos los dispositivos que probé, pero creo que la calidad puede ser mejor utilizando otro proceso que desconozco.

Espero que mi código pueda ayudar a otros desarrolladores en la misma situación. También aprecio que un desarrollador experimentado pueda ayudar, dando una sugerencia sobre otro proceso para evitar perder (menos) calidad en el proceso.