Coil — to biblioteka do ładowania obrazów na Androida, napisana w Kotlinie i oparta na korutynach. Według oficjalnej dokumentacji, biblioteka obsługuje Memory Cache, Disk Cache i transformacje z przyspieszeniem sprzętowym. Coil wyróżnia się minimalnym rozmiarem APK (około 150 KB) i pełną zgodnością z Jetpack Compose.
Najważniejsze
Coil (Coroutine Image Loader) — to biblioteka do ładowania obrazów na Androida, w całości napisana w Kotlinie i wykorzystująca korutyny do asynchronicznej pracy. Oferuje jednolity interfejs API do ładowania obrazów bitmapowych z sieci, zasobów, systemu plików i Content Provider, z automatycznym buforowaniem na wielu poziomach.
W przeciwieństwie do Glide i Picasso, Coil używa Kotlin Coroutines zamiast łańcuchów wywołań zwrotnych, co sprawia, że kod jest bardziej liniowy i przewidywalny. Wszystkie operacje ładowania i dekodowania są wykonywane w wątkach tła przez dyspozytora Dispatchers.IO, a wynik jest dostarczany do głównego wątku bez jawnego przełączania.
Coil obsługuje transformacje (Round, Blur, Grayscale), animacje przejść, SVG i GIF, a także niestandardowe Target dla nietypowego wyświetlania. Według Google I/O 2023, Coil jest zalecany w oficjalnych tutorialach Jetpack Compose na równi z Glide.
ImageLoader — główny komponent Coil odpowiedzialny za wykonywanie żądań ładowania i zarządzanie pamięcią podręczną. Każda instancja zawiera odniesienia do MemoryCache, DiskCache, BitmapPool i puli korutyn. Domyślnie używany jest singleton tworzony przez Coil.imageLoader(context).
ImageRequest — obiekt opisujący pojedyncze żądanie ładowania obrazu: źródło danych (URL, URI, zasób Int), docelowy ImageView lub Target, transformacje, ustawienia buforowania i placeholder. ImageRequest jest budowany przez builder, co zapewnia elastyczność i czytelność.
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()
Po zbudowaniu ImageRequest jest przekazywany do ImageLoader przez enqueue lub execute. Metoda enqueue uruchamia korutynę i zwraca Disposable, umożliwiający anulowanie ładowania przy opuszczaniu ekranu. Metoda execute to funkcja zawieszająca, zwracająca Result bezpośrednio.
ImageLoader kolejno sprawdza MemoryCache, DiskCache i dopiero przy braku trafienia w obu wykonuje żądanie sieciowe przez HttpEngine. Po załadowaniu bajty są dekodowane do Bitmap z uwzględnieniem docelowego rozmiaru, stosowane są transformacje, wynik jest zapisywany w obu pamięciach podręcznych i przekazywany do Target.
Coil jest zbudowany na architekturze komponentowej z możliwością wymiany dowolnej części przez Dependency Injection. Wszystkie komponenty są rejestrowane w ImageLoaderFactory i przekazywane do konstruktora ImageLoader przez builder.
ImageLoader — punkt wejścia dla wszystkich operacji ładowania. Każda instancja zawiera pulę korutyn, BitmapPool, MemoryCache, DiskCache i listę przechwytywaczy. Domyślnie tworzona jest jedna globalna instancja, ale do testów modułowych można tworzyć osobne egzemplarze z izolowaną pamięcią podręczną.
MemoryCache — pamięć podręczna in-memory oparta na LRU (Least Recently Used), przechowująca zdekodowane obiekty Bitmap. Domyślny maksymalny rozmiar to 25% dostępnej pamięci aplikacji, ale nie mniej niż 32 MB. Klucz pamięci podręcznej jest tworzony z URL + rozmiaru + transformacji, co eliminuje ryzyko zwrócenia nieaktualnego obrazu.
DiskCache — pamięć podręczna plików dla surowych danych (JPEG, PNG, WebP) i zdekodowanych metadanych. Znajduje się w katalogu pamięci podręcznej aplikacji i obsługuje automatyczne czyszczenie po przekroczeniu limitu. Praca z dyskiem jest wykonywana przez DiskCache.Builder z konfiguracją katalogu i maksymalnego rozmiaru.
Coil implementuje wielopoziomową strategię buforowania, minimalizującą żądania sieciowe i przyspieszającą wyświetlanie obrazów. Każdy poziom ma swój cel i czas życia danych.
| Poziom | Typ przechowywania | Czas życia | Domyślny rozmiar |
|---|---|---|---|
| Memory Cache | Bitmap w pamięci | Do wyparcia LRU | 25% heap, od 32 MB |
| Disk Cache | Pliki JPEG/WebP | Do przekroczenia limitu | 250 MB |
| Http Cache | Odpowiedzi OkHttp | Według nagłówków Cache-Control | Zależy od klienta HTTP |
Memory Cache zapewnia natychmiastowy dostęp do już zdekodowanych bitmap. Disk Cache gwarantuje działanie aplikacji bez sieci (offline-first) po pierwszym załadowaniu. Http Cache na poziomie OkHttp obsługuje warunkowe żądania ETag i If-Modified-Since.
Polityki buforowania są konfigurowane per-żądanie przez CachePolicy z trzema wartościami: ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. Na przykład dla awatarów użytkowników można ustawić READ_ONLY dla Memory Cache i ENABLED dla Disk Cache.
Coil oferuje kilka sposobów integracji w zależności od architektury aplikacji. Rozważymy trzy kluczowe scenariusze z działającymi przykładami kodu.
load — funkcja rozszerzająca dla ImageView, najprostszy sposób załadowania obrazu w jednej linijce. Funkcja przyjmuje URL, URI, zasób Int lub File oraz wszystkie opcjonalne parametry przez konfigurator lambda.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
Metoda load zwraca Disposable, który można anulować w onDestroy lub przy ponownym użyciu View. Zapobiega to wyciekom pamięci i zbędnym żądaniom sieciowym podczas szybkiego przewijania listy.
AsyncImage — funkcja composable do ładowania obrazów w deklaratywnym UI. Przyjmuje dowolne źródło danych i trzy opcjonalne parametry dla stanów: placeholder, error i success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "obraz sieciowy",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage — bardziej elastyczna wersja, pozwalająca dostosować wyświetlanie podczas ładowania przez slot content. Jest to przydatne dla szkieletów (shimmer) i pasków postępu.
Jeśli ImageView lub AsyncImage nie są odpowiednie, można zaimplementować Target z pojedynczą metodą onSuccess przyjmującą Bitmap. Jest to używane do ładowania w Notification, RemoteViews lub tekstury 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()
)
Wybór biblioteki do ładowania obrazów zależy od wymagań projektu. Coil konkuruje z Glide i Picasso, z których każda ma swoje mocne strony. Porównanie głównych cech przedstawiono w tabeli.
| Cecha | Coil | Glide | Picasso |
|---|---|---|---|
| Język | Kotlin (100%) | Java + Kotlin | Java |
| Rozmiar APK | ~150 KB | ~500 KB | ~120 KB |
| Korutyny | Wbudowane | Nie (callback) | Nie (callback) |
| Jetpack Compose | Natywne wsparcie | Przez akompaniament | Zewnętrzne |
| GIF/WebP | Tak (wbudowane) | Tak (wbudowane) | Nie |
| Zalecenie Google | Tak (I/O 2023) | Tak | Nie |
Dla nowych projektów w Kotlinie i Jetpack Compose Coil staje się naturalnym wyborem dzięki zerowej dodatkowej zależności od korutyn i minimalnemu rozmiarowi. Glide pozostaje preferowany dla złożonych scenariuszy z animacjami i podglądami wideo. Picasso ustępuje obu pod względem funkcjonalności, ale wygrywa prostotą.
Podłączenie Coil do projektu Android odbywa się przez zależność Gradle. Po dodaniu biblioteka automatycznie rejestruje ImageLoader przez ContentProvider, więc ręczna inicjalizacja w Application nie jest wymagana. W razie potrzeby dostosowania tworzy się własny ImageLoader przez builder.
// build.gradle.kts (moduł aplikacji)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// Dodatkowo dla Jetpack Compose:
implementation("io.coil-kt:coil-compose:2.6.0")
// Dla obsługi SVG:
implementation("io.coil-kt:coil-svg:2.6.0")
// Dla obsługi GIF:
implementation("io.coil-kt:coil-gif:2.6.0")
}
Do dostosowania ImageLoader używa się ImageLoaderFactory — singletonu tworzonego w Application.onCreate. W fabryce można skonfigurować limity pamięci podręcznej, klient HTTP, niestandardowe dekodery i logowanie. Domyślnie Coil używa OkHttp z gotową pulą połączeń.
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()
}
}
Często zadawane pytania
Coil — biblioteka do ładowania obrazów na Androida, napisana w Kotlinie z wykorzystaniem korutyn. Służy do asynchronicznego ładowania, buforowania i wyświetlania obrazów bitmapowych z sieci, zasobów lub systemu plików.
Coil jest napisany w 100% w Kotlinie i używa korutyn zamiast mechanizmu wywołań zwrotnych w Glide. Coil ma mniejszy rozmiar APK (~150 KB wobec ~500 KB) i natywne wsparcie dla Jetpack Compose przez AsyncImage.
Dodaj zależność io.coil-kt:coil:2.6.0 w build.gradle.kts. Dla Jetpack Compose dodaj również io.coil-kt:coil-compose:2.6.0. Biblioteka automatycznie rejestruje ImageLoader przez ContentProvider.
Coil obsługuje JPEG, PNG, WebP, BMP, SVG (przez moduł coil-svg) i GIF (przez moduł coil-gif). Formaty AVIF i HEIF są obsługiwane przez niestandardowy dekoder na urządzeniach z Androidem 10+.
Pamięć podręczną konfiguruje się przez ImageLoader.Builder: memoryCache z określeniem procentu dostępnej pamięci, diskCache ze ścieżką i limitem w bajtach. Polityki pamięci podręcznej (ENABLED, DISABLED, READ_ONLY) są konfigurowane per-żądanie przez CachePolicy.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również