Coil es una librería para cargar imágenes en Android, escrita en Kotlin y construida sobre corrutinas. Según la documentación oficial, la librería admite Memory Cache, Disk Cache y transformaciones con aceleración por hardware. Coil destaca por su tamaño mínimo de APK (aproximadamente 150 KB) y su total compatibilidad con Jetpack Compose.
Puntos clave
Coil (Coroutine Image Loader) es una librería para cargar imágenes en Android, escrita completamente en Kotlin y que utiliza corrutinas para operaciones asíncronas. Proporciona una API unificada para cargar imágenes de mapa de bits desde la red, recursos, sistema de archivos y Content Provider, con almacenamiento en caché automático de múltiples niveles.
A diferencia de Glide y Picasso, Coil utiliza Kotlin Coroutines en lugar de cadenas de callbacks, lo que hace que el código sea más lineal y predecible. Todas las operaciones de carga y decodificación se ejecutan en hilos de fondo a través del dispatcher Dispatchers.IO, y los resultados se entregan al hilo principal sin cambios explícitos.
Coil admite transformaciones (Round, Blur, Grayscale), animaciones de transición, SVG y GIF, así como Targets personalizados para visualización no estándar. Según Google I/O 2023, Coil está recomendado en tutoriales oficiales de Jetpack Compose junto con Glide.
ImageLoader es el componente principal de Coil, responsable de ejecutar las solicitudes de carga y gestionar la caché. Cada instancia contiene referencias a MemoryCache, DiskCache, BitmapPool y un grupo de corrutinas. Por defecto, se utiliza un singleton creado mediante Coil.imageLoader(context).
ImageRequest es un objeto que describe una única solicitud de carga de imagen: origen de datos (URL, URI, recurso Int), ImageView o Target de destino, transformaciones, configuración de caché y placeholder. ImageRequest se construye mediante un builder, lo que garantiza flexibilidad y legibilidad.
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.crossfade(true)
.size(512, 512)
.transformations(listOf(RoundedCornersTransformation(12f)))
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.target(imageView)
.build()
Una vez construido, ImageRequest se pasa a ImageLoader mediante enqueue o execute. El método enqueue inicia una corrutina y devuelve un Disposable, que permite cancelar la carga al salir de la pantalla. El método execute es una función suspend que devuelve un Result directamente.
ImageLoader verifica secuencialmente MemoryCache, DiskCache y solo si ambos fallan, ejecuta una solicitud de red a través de HttpEngine. Después de la carga, los bytes se decodifican en un Bitmap considerando el tamaño de destino, se aplican las transformaciones, el resultado se almacena en ambas cachés y se pasa al Target.
Coil está construido sobre una arquitectura de componentes con capacidad de reemplazar cualquier parte mediante Inyección de Dependencias. Todos los componentes se registran en ImageLoaderFactory y se pasan al constructor de ImageLoader a través del builder.
ImageLoader es el punto de entrada para todas las operaciones de carga. Cada instancia contiene un grupo de corrutinas, BitmapPool, MemoryCache, DiskCache y una lista de interceptores. Por defecto, se crea una instancia global, pero para pruebas unitarias se pueden crear instancias separadas con cachés aislados.
MemoryCache es una caché en memoria basada en LRU (Least Recently Used) que almacena objetos Bitmap decodificados. El tamaño máximo por defecto es el 25% de la memoria disponible de la aplicación, pero no menos de 32 MB. La clave de caché se forma a partir de URL + tamaño + transformaciones, lo que evita la recuperación de imágenes desactualizadas.
DiskCache es una caché basada en archivos para datos sin procesar (JPEG, PNG, WebP) y metadatos decodificados. Se ubica en el directorio de caché de la aplicación y admite limpieza automática al superar el límite. Las operaciones de disco se realizan mediante DiskCache.Builder con configuración de directorio y tamaño máximo.
Coil implementa una estrategia de almacenamiento en caché multinivel que minimiza las solicitudes de red y acelera la visualización de imágenes. Cada nivel tiene su propio propósito y tiempo de vida de los datos.
| Nivel | Tipo de almacenamiento | Tiempo de vida | Tamaño por defecto |
|---|---|---|---|
| Memory Cache | Bitmap en RAM | Hasta desalojo LRU | 25% del heap, desde 32 MB |
| Disk Cache | Archivos JPEG/WebP | Hasta superar el límite | 250 MB |
| Http Cache | Respuestas OkHttp | Según cabeceras Cache-Control | Depende del cliente HTTP |
Memory Cache proporciona acceso instantáneo a los Bitmaps ya decodificados. Disk Cache garantiza que la aplicación funcione sin red (offline-first) después de la primera carga. Http Cache a nivel de OkHttp maneja solicitudes condicionales con ETag e If-Modified-Since.
Las políticas de caché se configuran por solicitud mediante CachePolicy con tres valores: ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. Por ejemplo, para avatares de usuarios, se puede establecer READ_ONLY para Memory Cache y ENABLED para Disk Cache.
Coil proporciona varios métodos de integración según la arquitectura de la aplicación. Veamos tres escenarios clave con ejemplos de código funcionales.
load es una función de extensión para ImageView, la forma más sencilla de cargar una imagen en una línea. La función acepta una URL, URI, recurso Int o File, junto con todos los parámetros opcionales mediante un configurador lambda.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
El método load devuelve un Disposable, que se puede cancelar en onDestroy o al reutilizar la View. Esto evita fugas de memoria y solicitudes de red innecesarias durante el desplazamiento rápido de listas.
AsyncImage es una función composable para cargar imágenes en UI declarativa. Acepta cualquier fuente de datos y tres parámetros opcionales para estados: placeholder, error y success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "imagen de red",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage es una versión más flexible que permite personalizar la visualización durante la carga mediante un slot de contenido. Esto es útil para esqueletos (shimmer) y barras de progreso.
Si ImageView o AsyncImage no son adecuados, se puede implementar un Target con un único método onSuccess que acepta un Bitmap. Esto se utiliza para cargar en Notification, RemoteViews o texturas OpenGL.
val target = object : BitmapTarget() {
override fun onSuccess(result: Bitmap) {
notificationRemoteView.setImageViewBitmap(R.id.icon, result)
}
}
imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
.target(target)
.build()
)
La elección de la librería de carga de imágenes depende de los requisitos del proyecto. Coil compite con Glide y Picasso, cada una con sus puntos fuertes. En la tabla se presenta una comparación de las características principales.
| Característica | Coil | Glide | Picasso |
|---|---|---|---|
| Idioma | Kotlin (100%) | Java + Kotlin | Java |
| Tamaño del APK | ~150 KB | ~500 KB | ~120 KB |
| Corrutinas | Integradas | No (callbacks) | No (callbacks) |
| Jetpack Compose | Soporte nativo | Mediante accompanist | De terceros |
| GIF/WebP | Sí (integrado) | Sí (integrado) | No |
| Recomendación de Google | Sí (I/O 2023) | Sí | No |
Para proyectos nuevos en Kotlin y Jetpack Compose, Coil se convierte en la elección natural gracias a cero dependencias adicionales de corrutinas y tamaño mínimo. Glide sigue siendo preferible para escenarios complejos con animaciones y vistas previas de video. Picasso es inferior a ambas en funcionalidad, pero gana en simplicidad.
La incorporación de Coil a un proyecto Android se realiza mediante una dependencia de Gradle. Después de agregarla, la librería registra automáticamente un ImageLoader a través de ContentProvider, por lo que no se requiere inicialización manual en Application. Si se necesita personalización, se crea un ImageLoader personalizado mediante el builder.
// build.gradle.kts (app module)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// Para Jetpack Compose adicionalmente:
implementation("io.coil-kt:coil-compose:2.6.0")
// Para soporte SVG:
implementation("io.coil-kt:coil-svg:2.6.0")
// Para soporte GIF:
implementation("io.coil-kt:coil-gif:2.6.0")
}
Para personalizar ImageLoader, se utiliza ImageLoaderFactory — un singleton creado en Application.onCreate. En la fábrica se pueden configurar los límites de caché, el cliente HTTP, los decodificadores personalizados y el registro. Por defecto, Coil utiliza OkHttp con un grupo de conexiones preparado.
class App : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(cacheDir.resolve("coil_cache"))
.maxSizeBytes(512 * 1024 * 1024)
.build()
}
.build()
}
}
Preguntas frecuentes
Coil es una librería para cargar imágenes en Android, escrita en Kotlin y que utiliza corrutinas. Se utiliza para la carga asíncrona, almacenamiento en caché y visualización de imágenes de mapa de bits desde la red, recursos o sistema de archivos.
Coil está escrito 100% en Kotlin y utiliza corrutinas en lugar del mecanismo de callbacks de Glide. Coil tiene un tamaño de APK menor (~150 KB frente a ~500 KB) y soporte nativo para Jetpack Compose mediante AsyncImage.
Agregue la dependencia io.coil-kt:coil:2.6.0 a build.gradle.kts. Para Jetpack Compose, agregue también io.coil-kt:coil-compose:2.6.0. La librería registra automáticamente un ImageLoader mediante ContentProvider.
Coil admite JPEG, PNG, WebP, BMP, SVG (mediante el módulo coil-svg) y GIF (mediante el módulo coil-gif). Los formatos AVIF y HEIF se admiten mediante un decodificador personalizado en dispositivos con Android 10+.
La caché se configura mediante ImageLoader.Builder: memoryCache especificando el porcentaje del heap, diskCache con la ruta y el límite en bytes. Las políticas de caché (ENABLED, DISABLED, READ_ONLY) se configuran por solicitud mediante CachePolicy.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también