Sketch es una biblioteca de carga de imágenes optimizada para Compose Multiplatform y Android View. Además de las capacidades estándar, integra soporte para formatos como GIF, SVG, miniaturas de video y corrección de orientación Exif. Su diseño interno se basa en una arquitectura modular que divide el proceso de carga en componentes indepandientes, facilitando el mantenimiento y la extensibilidad.
Diseño Modular y Componentes Principales
El flujo de trabajo se distribuye en cinco módulos fundamentales que operan de manera coordinada:
- Módulo de Red: Gestiona la obtención de datos desde diversas fuentes (HTTP, HTTPS, archivos locales, recursos). Se implementa a través de la familia de módulos
sketch-http. - Sistema de Caché: Opera con una estrategia de tres niveles: caché en memoria, caché de resultados y caché de descargas, ubicados en el paquete
com.github.panpf.sketch.cache. - Decodificación: Selecciona el decodificador apropiado según el formato del archivo (por ejemplo,
GifDecoderoSvgDecoder). - Transformación: Aplica modificaciones a la imagen decodificada, como redimensionamiento, rotación o filtros, gestionado por las clases en
com.github.panpf.sketch.transform. - Renderizado: Dibuja la imagen final en la interfaz de usuario, ya sea en un
AsyncImagede Compose o en unImageViewtradicional.
Construcción de la Solicitud de Imagen
El punto de entrada para cualquier operación es la clase ImageRequest. Este objeto encapsula la URI, las dimensiones objetivo, las políticas de caché y los recursos de respaldo. El patrón Builder permite una configuración detallada:
val imageReq = ImageRequest.Builder(applicationContext, "https://cdn.example.com/assets/banner.png")
.size(800, 600)
.placeholderRes(R.drawable.ic_loading)
.errorRes(R.drawable.ic_failed)
.crossfade(durationMillis = 300)
.build()
Una vez configurada, la solicitud se envía al motor mediante sketch.enqueue(imageReq) para ejecución asíncrona, o sketch.execute(imageReq) para un enfoque síncrono dentro de una corrutina.
Estrategia de Caché de Tres Niveles
Para minimizar el consumo de red y los ciclos de CPU, Sketch evalúa seucencialmente tres capas de almacenamiento:
- Caché en Memoria (Memory Cache): Almacena objetos de imagen ya decodificados para un acceso instantáneo. Ideal para elementos que se muestran repetidamente en la pantallla.
- Caché de Resultados (Result Cache): Persiste en disco los datos de la imagen después de aplicar decodificación y transformaciones, evitando reprocesar la imagen original.
- Caché de Descargas (Download Cache): Guarda el flujo de bytes crudo obtenido de la red, útil cuando se requieren diferentes transformaciones de la misma fuente.
Obtención de Datos y Decodificación
Si la imagen no se encuentra en ninguna capa de caché, el sistema delega la tarea a un Fetcher. Dependiendo del esquema de la URI, se instancia un fetcher específico como HttpUriFetcher para recursos remotos o FileUriFetcher para almacenamiento local.
Con los datos en bruto disponibles, el Decoder entra en acción. Para imágenes estáticas se utilizan los decodificadores nativos o Skia, mientras que formatos especializados requieren extensiones como sketch-animated-gif o sketch-svg. Durante esta fase, también se lee y aplica la metadata Exif para corregir la orientación de la imagen automáticamente.
Transformaciones y Renderizado Final
Las imágenes decodificadas pueden pasar por una cadena de Transformation. Operaciones como CircleCropTransformation o ajustes de tamaño se aplican antes de que el bitmap final se entregue al Target.
En el entorno de Compose, el componente AsyncImage actúa como el target implícito:
AsyncImage(
model = "https://cdn.example.com/assets/profile.jpg",
contentDescription = "Avatar del usuario",
modifier = Modifier.clip(CircleShape)
)
Para el sistema de vistas clásico de Android, se utiliza la función de extensión sobre ImageView:
profileImageView.loadImage("https://cdn.example.com/assets/profile.jpg") {
crossfade(true)
}
Orquestación y Manejo de Resultados
La clase Sketch actúa como el orquestador central. A través de su constructor, se definen parámetros globales como los límites de memoria y los niveles de registro:
val sketchInstance = Sketch.Builder(appContext)
.logger(level = Logger.Level.Verbose)
.memoryCacheCapacity(30 * 1024 * 1024) // 30 MB
.build()
El resultado de una ejecución síncrona se encapsula en un objeto ImageResult, que permite inspeccionar el estado de la operación:
val outcome = sketchInstance.execute(imageReq)
when (outcome) {
is ImageResult.Success -> {
val drawable = outcome.image
val source = outcome.dataFrom // NETWORK, DISK_CACHE, MEMORY_CACHE
}
is ImageResult.Error -> {
val exception = outcome.throwable
}
}
Adaptación Multiplataforma
Al estar construido sobre Compose Multiplatform, Sketch abstrae las diferencias entre sistemas operativos. Mientras que en Android se apoya en BitmapFactory y OkHttp/Ktor, en iOS y Desktop delega el renderizado y la decodificación al motor Skia. En entornos web, la gestión de caché se limita a la memoria debido a las restricciones del navegador, y las peticiones de red se realizan mediante la API Fetch nativa.