Trabajar con imágenes puede generar problemas de rendimiento con rapidez si no tienes cuidado. Incluso un gráfico pequeño en un formato comprimido, como JPG o PNG, puede convertirse en un mapa de bits grande cuando se decodifica para mostrarlo. Si no usas los gráficos de manera eficiente, puedes tener problemas de memoria que pueden afectar el rendimiento de tu app y de otras apps del dispositivo. Sigue estas prácticas recomendadas para asegurarte de que tu app tenga el mejor rendimiento.
Usa bibliotecas de carga de imágenes
Puedes mejorar la eficiencia de tu app usando bibliotecas de carga de imágenes, como Coil (para proyectos que priorizan Kotlin) o Glide (para proyectos de Java). Estas bibliotecas reducen el uso de memoria de tu app, ya que realizan acciones como almacenar imágenes en caché, reducir el muestreo de gráficos cuando es necesario y reciclar objetos gráficos.
Reduce el muestreo de las imágenes
Asegúrate de usar el tamaño de imagen adecuado para tus necesidades. Debes evitar cargar una imagen grande de alta resolución en un contenedor pequeño (como una miniatura). En su lugar, usa la reducción de muestreo para reducir la imagen antes de decodificarla en la memoria.
Reducción de muestreo del cliente
Las bibliotecas de carga de imágenes, como Coil y Glide, controlan la reducción de muestreo automáticamente. Puedes configurar sus estrategias de reducción de muestreo con ImageLoader (para Coil) o DownsampleStrategy (para Glide). Si administras mapas de bits de forma manual, puedes usar inSampleSize para decodificar una versión más pequeña. Para hacerlo de forma segura, primero debes establecer inJustDecodeBounds en true para leer las dimensiones de la imagen sin asignar memoria, calcular el tamaño de la muestra, establecer inSampleSize en ese valor, establecer inJustDecodeBounds en false y, luego, decodificar la imagen.
Se prefiere el cambio de tamaño del servidor
Siempre que sea posible, solicita las dimensiones exactas de la imagen que necesitas directamente desde tu servidor de backend. Esto reduce el uso de la red y la huella de la memoria caché del disco, a la vez que proporciona un uso de memoria más ligero, ya que evita la sobrecarga de memoria que implica cambiar el tamaño de las imágenes en el dispositivo.
Puedes configurar las bibliotecas para que agreguen de forma dinámica el tamaño de la vista de destino a la URL de la imagen. Por ejemplo, Coil permite esto con interceptores personalizados, y Glide lo admite con cargadores de modelos personalizados (como BaseGlideUrlLoader).
Evita los tamaños de diseño sin restricciones
Para que los cargadores de imágenes reduzcan el muestreo (del cliente o del servidor) de manera eficaz, deben conocer el tamaño objetivo antes de ejecutar la solicitud.
Evita usar wrapContentSize o dejar dimensiones sin restricciones en elementos componibles que cargan imágenes remotas. Si estas bibliotecas no pueden inferir los límites de destino, recurren a cargar la imagen original en tamaño completo.
Esto puede provocar que se cargue una imagen mucho más grande de lo necesario, lo que aumenta
el uso de memoria y la latencia.
En su lugar, establece dimensiones explícitas en el elemento de imagen que admite composición (por ejemplo, con Modifier.size) o define una relación de aspecto. Esto permite que el motor de diseño calcule el objetivo de píxeles exacto por adelantado, que el cargador de imágenes puede usar para solicitar y decodificar el recurso con el tamaño correcto.
Proporciona recursos alternativos para diferentes tamaños de pantalla
Si envías imágenes con tu app, considera proporcionar elementos de diferentes tamaños para diferentes resoluciones de dispositivo. Esto puede ayudar a reducir el tamaño de descarga de la app en dispositivos y mejorar el rendimiento, ya que cargará una imagen de menor resolución en un dispositivo de menor resolución. Para obtener más información sobre cómo proporcionar mapas de bits alternativos para diferentes tamaños de dispositivos, consulta la documentación de mapas de bits alternativos.
No apliques padding directamente
A veces, es posible que debas agregar padding a una imagen. Por ejemplo, tal vez quieras que la imagen esté rodeada por un borde transparente para el formato letterbox.
En esas situaciones, no agregues el padding directamente a la imagen, ya que esto cambiaría sus dimensiones. En cambio, deja las dimensiones de la imagen como están y ajusta su ubicación en la pantalla con InsetDrawable.
Como alternativa, puedes agregar padding al elemento componible o a la vista que contiene la imagen.
Elige el formato de píxeles adecuado
Equilibra la memoria y la calidad eligiendo el formato de píxeles adecuado. Usa RGB_565 cuando no necesites transparencia; este formato usa la mitad de la memoria del formato ARGB_8888 predeterminado.
En Glide, puedes configurar esto con DecodeFormat. En Coil, puedes usar la propiedad bitmapConfig.
Usa vectores siempre que sea posible
En el caso de las imágenes compuestas por formas geométricas, un gráfico vectorial es mucho más pequeño que un mapa de bits y se escala sin problemas para cualquier densidad de pantalla. Cuando sea apropiado, usa elementos como ShapeDrawable para representar gráficos.
Libera y reutiliza mapas de bits cuando puedas
Los archivos gráficos grandes pueden ocupar mucha memoria. Para reducir su impacto, debes liberar o reutilizar los objetos gráficos siempre que puedas.
Si usas una biblioteca de carga de imágenes, asegúrate de liberar los mapas de bits en el grupo administrado de la biblioteca cuando ya no los necesites. La biblioteca puede reutilizar los objetos cuando sea necesario y mantiene un búfer de memoria disponible para necesidades futuras.
Si administras gráficos de forma manual, debes liberar los mapas de bits cuando termines de usarlos. Para ello, llama a Bitmap.recycle y descarta de inmediato la referencia Bitmap, en lugar de depender de la recolección de elementos no utilizados.
Otras sugerencias y trucos
En esta sección, se enumeran algunas otras formas de mejorar el rendimiento de tu app cuando se manejan gráficos.
No empaquetes imágenes grandes con el archivo AAB o APK
Una de las principales causas del tamaño grande de descarga de apps se debe a los gráficos que se empaquetan dentro del archivo AAB o APK. Usa la herramienta Analizador de APK para asegurarte de no empaquetar archivos de imagen más grandes de lo necesario. Reduce los tamaños o considera colocar las imágenes en un servidor y descargarlas solo cuando sea necesario.
Cómo encontrar mapas de bits redundantes
Si tienes varias copias de la misma imagen, se desperdicia memoria. Puedes usar el generador de perfiles de Android Studio para identificar gráficos redundantes. Usa el analizador de volcados de montón para capturar un volcado de montón y filtra los resultados eligiendo el parámetro de configuración duplicate bitmaps.
Cuando uses ImageBitmap, llama a prepareToDraw antes de dibujar
Cuando uses ImageBitmap, para comenzar el proceso de carga de textura en la GPU, llama a ImageBitmap#prepareToDraw() antes de dibujarla. Esto ayuda a la GPU a preparar la textura y mejorar el rendimiento de mostrar una imagen en la pantalla. La mayoría de las bibliotecas de carga de imágenes ya realizan esta optimización, pero, si trabajas con la clase ImageBitmap, es una cuestión que debes tener en cuenta.
Opta por pasar un Int DrawableRes o una URL como parámetros a tu elemento componible en lugar de Painter
Debido a las complejidades de lidiar con imágenes (por ejemplo, escribir una función equals para Bitmaps sería costoso desde el punto de vista del procesamiento), la API de Painter no está explícitamente marcada como estable con la anotación @Stable. Las clases inestables pueden generar recomposiciones innecesarias porque el compilador no puede deducir con facilidad si los datos cambiaron.
Por lo tanto, te recomendamos pasar una URL o un ID de recurso de elementos de diseño como parámetros al elemento componible, en lugar de pasar Painter como parámetro.
// Prefer this:
@Composable
fun MyImage(url: String) {
}
// Over this:
@Composable
fun MyImage(painter: Painter) {
}
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- ImageBitmap frente a ImageVector {:#bitmap-vs-vector}
- Cómo guardar el estado de la IU en Compose
- Fases de Jetpack Compose