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 eres eficiente con el uso de gráficos, puedes tener problemas de memoria que afecten el rendimiento de tu app y de otras apps en el 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 realizando acciones como almacenar imágenes en caché, reducir el muestreo de gráficos cuando es necesario y reciclar objetos gráficos.
Reduce la resolución de las imágenes
Asegúrate de usar el tamaño de imagen adecuado para tus necesidades. Debes evitar cargar una imagen grande en alta resolución en un contenedor pequeño (como una miniatura). En su lugar, usa la reducción de muestreo para reducir la escala de la imagen antes de decodificarla en la memoria.
Reducción del 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 muestras 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.
Prefiere el cambio de tamaño del servidor
Cuando 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 utilización 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 lo permite con interceptores personalizados, y Glide lo admite con cargadores de modelos personalizados (como BaseGlideUrlLoader).
Cómo evitar tamaños de diseño no restringidos
Para que los cargadores de imágenes realicen un muestreo inferior (en el cliente o en el 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, vuelven 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 tu elemento componible de imagen (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 del 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 relleno directamente
A veces, es posible que debas agregar padding a una imagen. Por ejemplo, es posible que desees que la imagen esté rodeada por un borde transparente para el letterboxing.
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.
También puedes agregar padding al elemento componible o a la vista que contiene la imagen.
Elige el formato de píxel adecuado
Elige el formato de píxel adecuado para equilibrar la memoria y la calidad. 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 cuando 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 ajusta sin problemas a cualquier densidad de pantalla. Cuando sea adecuado, 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 los gráficos de forma manual, debes liberar los mapas de bits cuando termines de usarlos llamando a Bitmap.recycle y descartando 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 controlan 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 volcado de montón para capturar un volcado de montón y filtra los resultados eligiendo el parámetro de configuración mapas de bits duplicados.
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, 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