Coil è una libreria per il caricamento di immagini su Android, scritta in Kotlin e basata su coroutine. Secondo la documentazione ufficiale, la libreria supporta Memory Cache, Disk Cache e trasformazioni con accelerazione hardware. Coil si distingue per le dimensioni minime dell'APK (circa 150 KB) e la piena compatibilità con Jetpack Compose.
Punti chiave
Coil (Coroutine Image Loader) è una libreria per il caricamento di immagini su Android, interamente scritta in Kotlin e che utilizza coroutine per operazioni asincrone. Fornisce un'API unificata per caricare immagini bitmap dalla rete, risorse, file system e Content Provider, con memorizzazione nella cache automatica a più livelli.
A differenza di Glide e Picasso, Coil utilizza Kotlin Coroutines invece di catene di callback, rendendo il codice più lineare e prevedibile. Tutte le operazioni di caricamento e decodifica vengono eseguite su thread in background tramite il dispatcher Dispatchers.IO, con risultati consegnati al thread principale senza commutazione esplicita.
Coil supporta trasformazioni (Round, Blur, Grayscale), animazioni di transizione, SVG e GIF, nonché Target personalizzati per visualizzazioni non standard. Secondo Google I/O 2023, Coil è raccomandato nei tutorial ufficiali di Jetpack Compose insieme a Glide.
ImageLoader è il componente principale di Coil, responsabile dell'esecuzione delle richieste di caricamento e della gestione della cache. Ogni istanza contiene riferimenti a MemoryCache, DiskCache, BitmapPool e un pool di coroutine. Per impostazione predefinita, viene utilizzato un singleton creato tramite Coil.imageLoader(context).
ImageRequest è un oggetto che descrive una singola richiesta di caricamento immagine: origine dati (URL, URI, risorsa Int), ImageView o Target di destinazione, trasformazioni, impostazioni della cache e placeholder. ImageRequest viene costruito tramite un builder, garantendo flessibilità e leggibilità.
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 volta costruito, ImageRequest viene passato a ImageLoader tramite enqueue o execute. Il metodo enqueue avvia una coroutine e restituisce un Disposable, consentendo di annullare il caricamento quando si esce dallo schermo. Il metodo execute è una funzione suspend che restituisce direttamente un Result.
ImageLoader controlla sequenzialmente MemoryCache, DiskCache e solo in caso di mancata corrispondenza di entrambi esegue una richiesta di rete tramite HttpEngine. Dopo il caricamento, i byte vengono decodificati in un Bitmap considerando la dimensione target, vengono applicate le trasformazioni, il risultato viene memorizzato in entrambe le cache e passato al Target.
Coil è costruito su un'architettura a componenti con la possibilità di sostituire qualsiasi parte tramite Dependency Injection. Tutti i componenti vengono registrati in ImageLoaderFactory e passati al costruttore di ImageLoader tramite il builder.
ImageLoader è il punto di ingresso per tutte le operazioni di caricamento. Ogni istanza contiene un pool di coroutine, BitmapPool, MemoryCache, DiskCache e un elenco di intercettatori. Per impostazione predefinita, viene creata un'istanza globale, ma per i test unitari è possibile creare istanze separate con cache isolate.
MemoryCache è una cache in memoria basata su LRU (Least Recently Used) che memorizza oggetti Bitmap decodificati. La dimensione massima predefinita è il 25% della memoria disponibile dell'applicazione, ma non inferiore a 32 MB. La chiave della cache è formata da URL + dimensione + trasformazioni, impedendo il recupero di immagini obsolete.
DiskCache è una cache basata su file per dati grezzi (JPEG, PNG, WebP) e metadati decodificati. Si trova nella directory della cache dell'applicazione e supporta la pulizia automatica quando il limite viene superato. Le operazioni su disco vengono eseguite tramite DiskCache.Builder con configurazione della directory e della dimensione massima.
Coil implementa una strategia di caching multilivello che minimizza le richieste di rete e accelera la visualizzazione delle immagini. Ogni livello ha il proprio scopo e durata dei dati.
| Livello | Tipo di archiviazione | Durata | Dimensione predefinita |
|---|---|---|---|
| Memory Cache | Bitmap in RAM | Fino allo sfratto LRU | 25% dell'heap, da 32 MB |
| Disk Cache | File JPEG/WebP | Fino al superamento del limite | 250 MB |
| Http Cache | Risposte OkHttp | Secondo le intestazioni Cache-Control | Dipende dal client HTTP |
Memory Cache fornisce accesso immediato ai Bitmap già decodificati. Disk Cache garantisce che l'app funzioni senza rete (offline-first) dopo il primo caricamento. Http Cache a livello OkHttp gestisce richieste condizionali con ETag e If-Modified-Since.
Le politiche di cache sono configurate per richiesta tramite CachePolicy con tre valori: ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. Ad esempio, per gli avatar degli utenti, è possibile impostare READ_ONLY per Memory Cache e ENABLED per Disk Cache.
Coil fornisce diversi metodi di integrazione a seconda dell'architettura dell'applicazione. Esaminiamo tre scenari chiave con esempi di codice funzionanti.
load è una funzione di estensione per ImageView, il modo più semplice per caricare un'immagine in una riga. La funzione accetta URL, URI, risorsa Int o File, insieme a tutti i parametri opzionali tramite un configuratore lambda.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
Il metodo load restituisce un Disposable, che può essere annullato in onDestroy o durante il riutilizzo della View. Ciò previene perdite di memoria e richieste di rete non necessarie durante lo scorrimento rapido delle liste.
AsyncImage è una funzione composable per caricare immagini in UI dichiarativa. Accetta qualsiasi fonte di dati e tre parametri opzionali per gli stati: placeholder, error e success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "immagine di rete",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage è una versione più flessibile che consente di personalizzare la visualizzazione durante il caricamento tramite uno slot di contenuto. Questo è utile per scheletri (shimmer) e barre di progresso.
Se ImageView o AsyncImage non sono adatti, è possibile implementare un Target con un singolo metodo onSuccess che accetta un Bitmap. Viene utilizzato per il caricamento in Notification, RemoteViews o texture 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 scelta della libreria di caricamento immagini dipende dai requisiti del progetto. Coil compete con Glide e Picasso, ciascuno con i propri punti di forza. Un confronto delle caratteristiche principali è presentato nella tabella.
| Caratteristica | Coil | Glide | Picasso |
|---|---|---|---|
| Linguaggio | Kotlin (100%) | Java + Kotlin | Java |
| Dimensioni APK | ~150 KB | ~500 KB | ~120 KB |
| Coroutine | Integrate | No (callback) | No (callback) |
| Jetpack Compose | Supporto nativo | Tramite accompanist | Di terze parti |
| GIF/WebP | Sì (integrato) | Sì (integrato) | No |
| Raccomandazione Google | Sì (I/O 2023) | Sì | No |
Per i nuovi progetti su Kotlin e Jetpack Compose, Coil diventa la scelta naturale grazie a zero dipendenze aggiuntive di coroutine e dimensioni minime. Glide rimane preferito per scenari complessi con animazioni e anteprime video. Picasso è inferiore a entrambi in funzionalità ma vince in semplicità.
L'aggiunta di Coil a un progetto Android avviene tramite una dipendenza Gradle. Dopo l'aggiunta, la libreria registra automaticamente un ImageLoader tramite ContentProvider, quindi non è necessaria l'inizializzazione manuale in Application. Se è necessaria la personalizzazione, viene creato un ImageLoader personalizzato tramite il builder.
// build.gradle.kts (app module)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// Per Jetpack Compose in aggiunta:
implementation("io.coil-kt:coil-compose:2.6.0")
// Per supporto SVG:
implementation("io.coil-kt:coil-svg:2.6.0")
// Per supporto GIF:
implementation("io.coil-kt:coil-gif:2.6.0")
}
Per personalizzare ImageLoader, viene utilizzato ImageLoaderFactory — un singleton creato in Application.onCreate. Nella factory è possibile configurare i limiti della cache, il client HTTP, i decoder personalizzati e la registrazione. Per impostazione predefinita, Coil utilizza OkHttp con un pool di connessioni pronto.
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()
}
}
Domande frequenti
Coil è una libreria per il caricamento di immagini su Android, scritta in Kotlin utilizzando coroutine. Viene utilizzata per il caricamento asincrono, la memorizzazione nella cache e la visualizzazione di immagini bitmap dalla rete, risorse o file system.
Coil è scritto al 100% in Kotlin e utilizza coroutine invece del meccanismo di callback di Glide. Coil ha dimensioni APK inferiori (~150 KB contro ~500 KB) e supporto nativo per Jetpack Compose tramite AsyncImage.
Aggiungi la dipendenza io.coil-kt:coil:2.6.0 in build.gradle.kts. Per Jetpack Compose, aggiungi anche io.coil-kt:coil-compose:2.6.0. La libreria registra automaticamente un ImageLoader tramite ContentProvider.
Coil supporta JPEG, PNG, WebP, BMP, SVG (tramite il modulo coil-svg) e GIF (tramite il modulo coil-gif). I formati AVIF e HEIF sono supportati tramite un decoder personalizzato su dispositivi con Android 10+.
La cache viene configurata tramite ImageLoader.Builder: memoryCache specificando la percentuale di heap, diskCache con percorso e limite in byte. Le politiche di cache (ENABLED, DISABLED, READ_ONLY) vengono configurate per richiesta tramite CachePolicy.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche