studio - manual de programacion android




¿La forma más eficiente de memoria para cambiar el tamaño de los mapas de bits en Android? (2)

Estoy creando una aplicación social de uso intensivo de imágenes donde las imágenes se envían desde el servidor al dispositivo. Cuando el dispositivo tiene resoluciones de pantalla más pequeñas, necesito cambiar el tamaño de los mapas de bits, en el dispositivo, para que coincidan con los tamaños de pantalla deseados.

El problema es que el uso de createScaledBitmap hace que me encuentre con muchos errores de createScaledBitmap de memoria después de cambiar el tamaño de una horda de imágenes en miniatura.

¿Cuál es la forma más eficiente de memoria para cambiar el tamaño de los mapas de bits en Android?


Esta respuesta se resume en Carga eficiente de mapas de bits grandes que explica cómo usar inSampleSize para cargar una versión de mapa de bits reducida.

En particular , los mapas de bits de preescalado explican los detalles de varios métodos, cómo combinarlos y cuáles son los más eficientes en memoria.

Hay tres formas dominantes de cambiar el tamaño de un mapa de bits en Android que tienen diferentes propiedades de memoria:

createScaledBitmap

Esta API tomará un mapa de bits existente y creará un NUEVO mapa de bits con las dimensiones exactas que ha seleccionado.

En el lado positivo, puede obtener exactamente el tamaño de imagen que está buscando (independientemente de cómo se vea). Pero la desventaja es que esta API requiere un mapa de bits existente para funcionar . Lo que significa que la imagen tendría que cargarse, decodificarse y crearse un mapa de bits, antes de poder crear una nueva versión más pequeña. Esto es ideal en términos de obtener sus dimensiones exactas, pero horrible en términos de sobrecarga de memoria adicional. Como tal, esto es una especie de factor decisivo para la mayoría de los desarrolladores de aplicaciones que tienden a ser conscientes de la memoria

bandera inSampleSize

BitmapFactory.Options tiene una propiedad anotada como inSampleSize que redimensionará su imagen mientras la decodifica, para evitar la necesidad de decodificar en un mapa de bits temporal. Este valor entero utilizado aquí cargará una imagen con un tamaño reducido de 1 / x. Por ejemplo, establecer inSampleSize en 2 devuelve una imagen que es la mitad del tamaño, y Configurarlo en 4 devuelve una imagen que es 1/4 de tamaño. Básicamente, los tamaños de imagen siempre serán una potencia de dos más pequeños que el tamaño de origen.

Desde una perspectiva de memoria, usar inSampleSize es una operación realmente rápida. Efectivamente, solo decodificará cada píxel X de su imagen en su mapa de bits resultante. Sin embargo, hay dos problemas principales con inSampleSize :

  • No te da resoluciones exactas . Solo disminuye el tamaño de su mapa de bits en una potencia de 2.

  • No produce el cambio de tamaño de la mejor calidad . La mayoría de los filtros de redimensionamiento producen imágenes atractivas al leer bloques de píxeles y luego ponderarlos para producir el píxel redimensionado en cuestión. inSampleSize evita todo esto simplemente leyendo cada pocos píxeles. El resultado es bastante eficiente y tiene poca memoria, pero la calidad sufre.

Si solo está tratando de reducir su imagen en algún tamaño pow2, y el filtrado no es un problema, entonces no puede encontrar un método más eficiente en memoria (o rendimiento eficiente) que en inSampleSize .

Banderas inScaled, inDensity, inTargetDensity

Si necesita escalar una imagen a una dimensión que no sea igual a una potencia de dos, necesitará los inScaled , inDensity e inTargetDensity de BitmapOptions . Cuando se haya establecido el indicador inScaled , el sistema obtendrá el valor de escala para aplicar a su mapa de bits dividiendo inTargetDensity por los valores de inDensity .

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(), 
      mImageIDs, mBitmapOptions);

El uso de este método redimensionará su imagen y también le aplicará un 'filtro de cambio de tamaño', es decir, el resultado final se verá mejor porque se han tenido en cuenta algunas matemáticas adicionales durante el paso de cambio de tamaño. Pero tenga cuidado: ese paso de filtro adicional, requiere tiempo de procesamiento adicional y puede sumar rápidamente para imágenes grandes, lo que resulta en cambios de tamaño lentos y asignaciones de memoria adicionales para el filtro en sí.

Por lo general, no es una buena idea aplicar esta técnica a una imagen que sea significativamente más grande que el tamaño deseado, debido a la sobrecarga de filtrado adicional.

Combinación mágica

Desde una perspectiva de memoria y rendimiento, puede combinar estas opciones para obtener los mejores resultados. (configuración de los inSampleSize , inScaled , inDensity e inTargetDensity )

inSampleSize aplicará primero a la imagen, llevándola a la siguiente potencia de dos MAYOR que su tamaño objetivo. Luego, inDensity e inTargetDensity se utilizan para escalar el resultado a las dimensiones exactas que desee, aplicando una operación de filtro para limpiar la imagen.

Combinar estos dos es una operación mucho más rápida, ya que el paso inSampleSize reducirá la cantidad de píxeles en los que el paso resultante basado en la densidad necesitará aplicar su filtro de cambio de tamaño.

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;

// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

Si necesita ajustar una imagen a dimensiones específicas y un filtrado más agradable, entonces esta técnica es el mejor puente para obtener el tamaño correcto, pero se realiza en una operación rápida y de poca memoria.

Obteniendo dimensiones de imagen

Obtener el tamaño de la imagen sin decodificar la imagen completa Para cambiar el tamaño de su mapa de bits, necesitará conocer las dimensiones entrantes. Puede usar el indicador inJustDecodeBounds para ayudarlo a obtener las dimensiones de la imagen, sin necesidad de decodificar los datos de píxeles.

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;


//now go resize the image to the size you want

Puede usar esta bandera para decodificar el tamaño primero y luego calcular los valores adecuados para escalar a la resolución de su objetivo.


Tan agradable (y precisa) como es esta respuesta, también es muy complicada. En lugar de reinventar la rueda, considere bibliotecas como Glide , Picasso , UIL , Ion o cualquier otra que implemente esta lógica compleja y propensa a errores.

El propio Colt incluso recomienda echar un vistazo a Glide y Picasso en el video de patrones de rendimiento de mapas de bits de preescalado .

Al usar bibliotecas, puede obtener toda la eficiencia mencionada en la respuesta de Colt, pero con API mucho más simples que funcionan de manera consistente en todas las versiones de Android.





out-of-memory