Coil est une bibliothèque de chargement d'images pour Android, écrite en Kotlin et construite sur les coroutines. Selon la documentation officielle, la bibliothèque prend en charge le Memory Cache, le Disk Cache et les transformations avec accélération matérielle. Coil se distingue par sa taille d'APK minimale (environ 150 Ko) et sa compatibilité totale avec Jetpack Compose.
Points clés
Coil (Coroutine Image Loader) est une bibliothèque de chargement d'images pour Android, entièrement écrite en Kotlin et utilisant les coroutines pour les opérations asynchrones. Elle fournit une API unifiée pour charger des images bitmap depuis le réseau, les ressources, le système de fichiers et le Content Provider, avec une mise en cache automatique à plusieurs niveaux.
Contrairement à Glide et Picasso, Coil utilise Kotlin Coroutines au lieu de chaînes de callbacks, rendant le code plus linéaire et prévisible. Toutes les opérations de chargement et de décodage s'exécutent sur des threads d'arrière-plan via le dispatcher Dispatchers.IO, et les résultats sont livrés au thread principal sans commutation explicite.
Coil prend en charge les transformations (Round, Blur, Grayscale), les animations de transition, SVG et GIF, ainsi que des Targets personnalisées pour un affichage non standard. Selon Google I/O 2023, Coil est recommandé dans les tutoriels officiels Jetpack Compose aux côtés de Glide.
ImageLoader est le composant principal de Coil, responsable de l'exécution des requêtes de chargement et de la gestion du cache. Chaque instance contient des références à MemoryCache, DiskCache, BitmapPool et un pool de coroutines. Par défaut, un singleton créé via Coil.imageLoader(context) est utilisé.
ImageRequest est un objet qui décrit une seule requête de chargement d'image : source de données (URL, URI, ressource Int), ImageView ou Target de destination, transformations, paramètres de cache et placeholder. ImageRequest est construit via un builder, garantissant flexibilité et lisibilité.
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()
Une fois construit, ImageRequest est transmis à ImageLoader via enqueue ou execute. La méthode enqueue lance une coroutine et retourne un Disposable, permettant d'annuler le chargement en quittant l'écran. La méthode execute est une fonction suspend qui retourne directement un Result.
ImageLoader vérifie séquentiellement MemoryCache, DiskCache et seulement en cas d'échec des deux, exécute une requête réseau via HttpEngine. Après le chargement, les octets sont décodés en Bitmap en tenant compte de la taille cible, les transformations sont appliquées, le résultat est stocké dans les deux caches et transmis au Target.
Coil est construit sur une architecture de composants avec la possibilité de remplacer n'importe quelle partie via l'injection de dépendances. Tous les composants sont enregistrés dans ImageLoaderFactory et transmis au constructeur d'ImageLoader via le builder.
ImageLoader est le point d'entrée pour toutes les opérations de chargement. Chaque instance contient un pool de coroutines, BitmapPool, MemoryCache, DiskCache et une liste d'intercepteurs. Par défaut, une instance globale est créée, mais pour les tests unitaires, des instances séparées avec des caches isolés peuvent être créées.
MemoryCache est un cache en mémoire basé sur LRU (Least Recently Used) qui stocke des objets Bitmap décodés. La taille maximale par défaut est de 25 % de la mémoire disponible de l'application, mais pas moins de 32 Mo. La clé du cache est formée à partir de l'URL + taille + transformations, ce qui empêche la récupération d'images obsolètes.
DiskCache est un cache basé sur des fichiers pour les données brutes (JPEG, PNG, WebP) et les métadonnées décodées. Il est situé dans le répertoire de cache de l'application et prend en charge le nettoyage automatique en cas de dépassement de la limite. Les opérations sur disque sont effectuées via DiskCache.Builder avec configuration du répertoire et de la taille maximale.
Coil implémente une stratégie de mise en cache à plusieurs niveaux qui minimise les requêtes réseau et accélère l'affichage des images. Chaque niveau a son propre objectif et sa durée de vie des données.
| Niveau | Type de stockage | Durée de vie | Taille par défaut |
|---|---|---|---|
| Memory Cache | Bitmap en RAM | Jusqu'à l'éviction LRU | 25 % du heap, à partir de 32 Mo |
| Disk Cache | Fichiers JPEG/WebP | Jusqu'au dépassement de la limite | 250 Mo |
| Http Cache | Réponses OkHttp | Selon les en-têtes Cache-Control | Dépend du client HTTP |
Memory Cache offre un accès instantané aux Bitmaps déjà décodés. Disk Cache garantit le fonctionnement de l'application sans réseau (offline-first) après le premier chargement. Http Cache au niveau OkHttp gère les requêtes conditionnelles avec ETag et If-Modified-Since.
Les politiques de cache sont configurées par requête via CachePolicy avec trois valeurs : ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. Par exemple, pour les avatars utilisateurs, on peut définir READ_ONLY pour Memory Cache et ENABLED pour Disk Cache.
Coil propose plusieurs méthodes d'intégration selon l'architecture de l'application. Examinons trois scénarios clés avec des exemples de code fonctionnels.
load est une fonction d'extension pour ImageView, la façon la plus simple de charger une image en une ligne. La fonction accepte une URL, URI, ressource Int ou File, ainsi que tous les paramètres optionnels via un configurateur lambda.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
La méthode load retourne un Disposable, qui peut être annulé dans onDestroy ou lors de la réutilisation de la View. Cela évite les fuites mémoire et les requêtes réseau inutiles lors du défilement rapide de listes.
AsyncImage est une fonction composable pour charger des images dans une UI déclarative. Elle accepte n'importe quelle source de données et trois paramètres optionnels pour les états : placeholder, error et success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "image réseau",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage est une version plus flexible qui permet de personnaliser l'affichage pendant le chargement via un slot de contenu. C'est utile pour les squelettes (shimmer) et les barres de progression.
Si ImageView ou AsyncImage ne conviennent pas, vous pouvez implémenter un Target avec une seule méthode onSuccess qui accepte un Bitmap. Cela est utilisé pour le chargement dans Notification, RemoteViews ou les textures 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()
)
Le choix de la bibliothèque de chargement d'images dépend des exigences du projet. Coil est en concurrence avec Glide et Picasso, chacun ayant ses points forts. Un comparatif des principales caractéristiques est présenté dans le tableau.
| Caractéristique | Coil | Glide | Picasso |
|---|---|---|---|
| Langue | Kotlin (100 %) | Java + Kotlin | Java |
| Taille APK | ~150 Ko | ~500 Ko | ~120 Ko |
| Coroutines | Intégrées | Non (callbacks) | Non (callbacks) |
| Jetpack Compose | Support natif | Via accompanist | Tiers |
| GIF/WebP | Oui (intégré) | Oui (intégré) | Non |
| Recommandation Google | Oui (I/O 2023) | Oui | Non |
Pour les nouveaux projets en Kotlin et Jetpack Compose, Coil devient le choix naturel grâce à zéro dépendance supplémentaire aux coroutines et une taille minimale. Glide reste préféré pour les scénarios complexes avec animations et aperçus vidéo. Picasso est inférieur aux deux en fonctionnalité mais gagne en simplicité.
L'ajout de Coil à un projet Android se fait via une dépendance Gradle. Après l'ajout, la bibliothèque enregistre automatiquement un ImageLoader via ContentProvider, donc aucune initialisation manuelle dans Application n'est nécessaire. Si une personnalisation est nécessaire, un ImageLoader personnalisé est créé via le builder.
// build.gradle.kts (app module)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// Pour Jetpack Compose en supplément :
implementation("io.coil-kt:coil-compose:2.6.0")
// Pour le support SVG :
implementation("io.coil-kt:coil-svg:2.6.0")
// Pour le support GIF :
implementation("io.coil-kt:coil-gif:2.6.0")
}
Pour personnaliser ImageLoader, on utilise ImageLoaderFactory — un singleton créé dans Application.onCreate. Dans la fabrique, vous pouvez configurer les limites de cache, le client HTTP, les décodeurs personnalisés et la journalisation. Par défaut, Coil utilise OkHttp avec un pool de connexions prêt.
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()
}
}
Questions fréquentes
Coil est une bibliothèque de chargement d'images pour Android, écrite en Kotlin utilisant des coroutines. Elle est utilisée pour le chargement asynchrone, la mise en cache et l'affichage d'images bitmap depuis le réseau, les ressources ou le système de fichiers.
Coil est écrit à 100 % en Kotlin et utilise des coroutines au lieu du mécanisme de callbacks de Glide. Coil a une taille d'APK plus petite (~150 Ko contre ~500 Ko) et un support natif de Jetpack Compose via AsyncImage.
Ajoutez la dépendance io.coil-kt:coil:2.6.0 dans build.gradle.kts. Pour Jetpack Compose, ajoutez également io.coil-kt:coil-compose:2.6.0. La bibliothèque enregistre automatiquement un ImageLoader via ContentProvider.
Coil prend en charge JPEG, PNG, WebP, BMP, SVG (via le module coil-svg) et GIF (via le module coil-gif). Les formats AVIF et HEIF sont pris en charge via un décodeur personnalisé sur les appareils avec Android 10+.
Le cache se configure via ImageLoader.Builder : memoryCache avec le pourcentage du heap, diskCache avec le chemin et la limite en octets. Les politiques de cache (ENABLED, DISABLED, READ_ONLY) se configurent par requête via CachePolicy.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi