Buforowanie i wydajność
Podstawy buforowania w pamięci i na dysku
Buforowanie i wydajność to bezpłatna lekcja Android Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Android Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Android Academy zawiera 4 lekcji w sumie.
Dlaczego buforowanie ma znaczenie
Pobieranie i dekodowanie obrazu jest kosztowne: wykorzystuje transfer danych, baterię i procesor. Jeśli za każdym razem, gdy lista jest przewijana, pobierają Państwo ponownie ten sam obraz, aplikacja działa wolno i zużywa dużo danych.
Pamięć podręczna przechowuje zdekodowane obrazy i pobrane bajty, dzięki czemu kolejne żądanie jest niemal natychmiastowe. Coil domyślnie korzysta z dwóch pamięci podręcznych: pamięci podręcznej w pamięci (szybkie, zdekodowane bitmapy) oraz pamięci podręcznej na dysku (trwałe, surowe bajty).
W tej lekcji wyjaśniono, jak działają oba mechanizmy i jak je dostosowywać.
Dwie warstwy: pamięć i dysk
Coil sprawdza pamięci podręczne w następującej kolejności:
- Pamięć podręczna w pamięci: przechowuje ostatnio używane, zdekodowane bitmapy w pamięci RAM. Znalezienie obrazu tutaj jest natychmiastowe i nie wymaga dekodowania.
- Pamięć podręczna na dysku: przechowuje na urządzeniu oryginalne pobrane bajty. Znalezienie obrazu tutaj pomija sieć, ale nadal wymaga dekodowania.
- Sieć: najwolniejsza ścieżka, używana tylko wtedy, gdy nie uda się znaleźć obrazu w żadnej z pamięci podręcznych.
Pamięć jest szybka, ale mała i jest czyszczona po zakończeniu działania aplikacji; dysk jest większy i zachowuje dane po ponownym uruchomieniu aplikacji.
Konfigurowanie ImageLoader
Pamięci podręczne należą do ImageLoader, czyli silnika obsługującego każde żądanie. Zwykle warto utworzyć jeden niestandardowy loader i używać go ponownie. W tym przykładzie rozmiar pamięci podręcznej w pamięci jest ustalany jako procent dostępnej pamięci RAM, a rozmiar pamięci podręcznej na dysku jako stały limit bajtów.
Singleton loader pozwala uniknąć duplikowania pamięci podręcznych i marnowania pamięci.
import android.content.Context
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import okio.Path.Companion.toOkioPath
fun newImageLoader(context: Context): ImageLoader =
ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, 0.25) // 25% of app RAM
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache").toOkioPath())
.maxSizeBytes(50L * 1024 * 1024) // 50 MB
.build()
}
.build()Udostępnianie singleton loadera
Coil 3 pozwala ustawić jeden loader dla całej aplikacji, implementując SingletonImageLoader.Factory w klasie Application. Od tej pory każdy AsyncImage będzie używać go automatycznie i nie trzeba za każdym razem przekazywać loadera.
import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.build()
}Sterowanie zasadami buforowania
Każde żądanie może włączać lub wyłączać buforowanie za pomocą memoryCachePolicy i diskCachePolicy. Dostępne zasady to ENABLED, READ_ONLY, WRITE_ONLY oraz DISABLED.
Można na przykład wyłączyć pamięć podręczną w pamięci dla bardzo dużych, jednorazowo używanych obrazów, których nie chcą Państwo przechowywać w RAM-ie, albo wyłączyć pamięć podręczną na dysku dla poufnych treści.
import android.content.Context
import coil3.request.CachePolicy
import coil3.request.ImageRequest
fun noMemoryCacheRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.build()Zmiana rozmiaru: nie dekoduj ogromnych bitmap
Najpoważniejszym błędem wydajnościowym jest zdekodowanie zdjęcia o rozmiarze 4000x3000 do miniatury 100dp. Powoduje to niepotrzebne zużycie pamięci i czasu. Domyślnie Coil dopasowuje rozmiar bitmapy do zmierzonych wymiarów composable, więc mały AsyncImage dekoduje małą bitmapę.
Jeśli znają Państwo wymiary, mogą wymusić jawny rozmiar docelowy za pomocą size() w żądaniu.
import android.content.Context
import coil3.request.ImageRequest
import coil3.size.Size
fun thumbnailRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.size(Size(200, 200)) // decode at most 200x200
.build()Stabilne klucze pamięci podręcznej
Coil wyznacza klucz pamięci podręcznej na podstawie danych żądania. Jeśli adresy URL zawierają zmienne parametry zapytania (na przykład podpisany token), identyczne obrazy otrzymują różne klucze i nigdy nie są znajdowane w pamięci podręcznej.
Należy ustawić jawne memoryCacheKey i diskCacheKey na podstawie stabilnej części danych, takiej jak identyfikator obrazu, aby buforowanie działało prawidłowo.
import android.content.Context
import coil3.request.ImageRequest
fun stableKeyRequest(context: Context, id: String, signedUrl: String) =
ImageRequest.Builder(context)
.data(signedUrl)
.memoryCacheKey("image_$id")
.diskCacheKey("image_$id")
.build()Wstępne ładowanie obrazów
Aby zapewnić płynniejsze działanie, można zapełnić pamięć podręczną przed wyświetleniem obrazu, na przykład pobierając wstępnie następną stronę kanału. Należy użyć enqueue loadera wraz z żądaniem, które nie ma celu.
Gdy użytkownik dotrze do obrazu, będzie on już zapisany w pamięci podręcznej i wyświetli się natychmiast.
import android.content.Context
import coil3.ImageLoader
import coil3.request.ImageRequest
fun preload(context: Context, loader: ImageLoader, urls: List<String>) {
urls.forEach { url ->
val request = ImageRequest.Builder(context)
.data(url)
.build()
loader.enqueue(request)
}
}Czyszczenie pamięci podręcznej
Czasami trzeba zwolnić miejsce lub wymusić odświeżenie, na przykład po zmianie awatara przez użytkownika. Można wyczyścić pamięć podręczną w pamięci, pamięć podręczną na dysku albo usunąć pojedynczy klucz.
Wyczyszczenie wszystkiego jest kosztowne, dlatego jeśli to możliwe, lepiej usuwać konkretne klucze.
import coil3.ImageLoader
import coil3.memory.MemoryCache
fun refreshAvatar(loader: ImageLoader, key: String) {
// Remove one entry so the next load refetches
loader.memoryCache?.remove(MemoryCache.Key(key))
loader.diskCache?.remove(key)
}
fun clearAll(loader: ImageLoader) {
loader.memoryCache?.clear()
// Disk clearing is heavier; do it sparingly
}Crossfade a trafienia w pamięci podręcznej
Efekt crossfade świetnie sprawdza się przy ładowaniu z sieci, ale może powodować wrażenie opóźnienia, gdy obraz pochodzi bezpośrednio z pamięci podręcznej w pamięci i mógłby pojawić się natychmiast. Coil inteligentnie to obsługuje: domyślnie pomija animację crossfade dla obrazów znalezionych w pamięci podręcznej w pamięci i animuje tylko obrazy rzeczywiście załadowane.
Dzięki temu przy pierwszym ładowaniu otrzymują Państwo płynne zanikanie, a przy przewijaniu obrazów zapisanych w pamięci podręcznej — natychmiastowe wyświetlanie, bez konieczności wykonywania dodatkowej pracy.
import android.content.Context
import coil3.request.ImageRequest
import coil3.request.crossfade
fun feedRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
// Crossfade plays on real loads; memory-cache hits show instantly
.crossfade(true)
.build()Lista kontrolna wydajności list
Podczas wyświetlania wielu obrazów w LazyColumn lub siatce należy przestrzegać poniższych zasad, aby przewijanie było płynne:
- Należy nadać każdemu obrazowi stały rozmiar, aby Coil dekodował małą bitmapę.
- Należy używać jednego singleton
ImageLoader, aby pamięci podręczne były współdzielone. - Należy użyć
ContentScale.Crop, aby uniknąć przesuwania układu. - Klucze pamięci podręcznej powinny pozostać stabilne między kolejnymi przeładowaniami.
- Należy pozwolić Coil automatycznie anulować żądania obrazów poza ekranem — nie należy z tym walczyć.
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage
@Composable
fun PhotoGrid(urls: List<String>) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(urls) { url ->
AsyncImage(
model = url,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(1f)
)
}
}
}Szybkie sprawdzenie
Coil sprawdza pamięci podręczne przed połączeniem z siecią. Która pamięć podręczna jest najszybsza po znalezieniu obrazu, ponieważ całkowicie pomija dekodowanie?
Podsumowanie
Dowiedzieli się Państwo, jak Coil przyspiesza obsługę obrazów:
- Dwie pamięci podręczne: pamięć (zdekodowana, szybka, mała) i dysk (surowe bajty, trwała, większa).
- Rozmiary można skonfigurować w niestandardowym
ImageLoader, a jeden loader udostępnić za pomocąSingletonImageLoader.Factory. - Obsługę poszczególnych żądań można dostosować za pomocą
memoryCachePolicyidiskCachePolicy. - Zawsze należy dekodować obraz w rozmiarze wyświetlania; w razie potrzeby należy ustawić jawny
size(). - Należy używać stabilnych kluczy pamięci podręcznej, wstępnie ładować nadchodzące obrazy i usuwać konkretne klucze, aby odświeżać dane.
Następnie: wyjście poza obrazy — odtwarzanie dźwięku i wideo.
Często zadawane pytania
Czy lekcja „Buforowanie i wydajność” jest bezpłatna?
Tak — pełny tekst „Buforowanie i wydajność” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Android Academy, przejdź na CoddyKit PRO. Kurs Android Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Buforowanie i wydajność”?
Podstawy buforowania w pamięci i na dysku Ćwiczysz Android Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Android Academy?
Nie wymagamy żadnego doświadczenia. Android Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Buforowanie i wydajność”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Android Academy?
Tak. Każda lekcja Android Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Wczytywanie obrazów za pomocą Coil
- Elementy zastępcze i stany błędów
- Buforowanie i wydajność
- Odtwarzanie dźwięku i obrazu