Trabajar con imágenes puede generar problemas de rendimiento con rapidez si no tienes cuidado. Incluso un pequeño gráfico en un formato comprimido como JPG o PNG puede convertirse en un mapa de bits grande cuando se decodifica para mostrarse. Si no eres eficiente con la forma en que usas los gráficos, puedes tener problemas de memoria que pueden afectar el rendimiento de tu app y 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 con bibliotecas de carga de imágenes como Coil (para proyectos de Kotlin) o Glide (para proyectos de Java). Estas bibliotecas reducen el uso de memoria de tu app con acciones como almacenar imágenes en caché, reducir la resolución de los 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 de 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 de resolución del cliente
Las bibliotecas de carga de imágenes, como Coil y Glide, controlan la reducción de resolución automáticamente. Puedes configurar sus estrategias de reducción de resolución
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, debes
primero 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 huella de la memoria caché del disco, al tiempo que proporciona un uso de memoria más ligero, ya que evita la sobrecarga de memoria del cambio de tamaño de las imágenes en el dispositivo.
Puedes configurar bibliotecas para agregar 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 la resolución (del cliente o del servidor) de manera eficaz, deben conocer el tamaño de destino antes de ejecutar la solicitud.
Evita usar wrapContentSize o dejar dimensiones sin restricciones en elementos componibles que carguen imágenes remotas. Si estas bibliotecas no pueden inferir los límites de destino, vuelven a cargar la imagen original de tamaño completo.
Esto puede generar la carga de 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 exacto de píxeles 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 padding 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 cambiarás
sus dimensiones. En su lugar, 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 la vista que contiene la imagen.
Elige el formato de píxeles correcto
Equilibra la memoria y la calidad eligiendo el formato de píxeles correcto. 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 bitmapConfig propiedad.
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 para 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 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
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 controlas 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.
Busca 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 volcado de montón analizador para capturar un volcado de montón y filtrar los resultados eligiendo la configuración de 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, 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