Кэширование и производительность
Основы кэширования в памяти и на диске
«Кэширование и производительность» — бесплатный урок Android Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.
Зачем нужно кэширование
Загрузка и декодирование изображения требуют значительных ресурсов: используются пропускная способность сети, заряд батареи и CPU. Если загружать одно и то же изображение заново при каждом прокручивании списка, приложение работает медленно и расходует трафик.
Кэширование сохраняет декодированные изображения и загруженные байты, поэтому следующий запрос выполняется почти мгновенно. По умолчанию Coil предоставляет два кэша: кэш в памяти (быстрые декодированные растровые изображения) и дисковый кэш (сохраняемые исходные байты).
В этом уроке вы разберётесь, как они работают и как настроить их параметры.
Два уровня: память и диск
Coil проверяет кэши по очереди:
- Кэш в памяти: хранит недавно использованные декодированные растровые изображения в RAM. Попадание в этот кэш происходит мгновенно, декодирование не требуется.
- Дисковый кэш: хранит исходные загруженные байты на устройстве. Попадание в этот кэш позволяет пропустить сетевой запрос, но декодирование всё равно выполняется.
- Сеть: медленный путь, который используется только при отсутствии изображения в обоих кэшах.
Память работает быстро, но имеет небольшой объём и очищается при завершении приложения; диск вмещает больше данных и сохраняется после перезапуска.
Настройка ImageLoader
Кэши находятся в ImageLoader — механизме, который обслуживает каждый запрос. Обычно создают один пользовательский загрузчик и повторно используют его. Здесь размер кэша в памяти задаётся как процент доступной RAM, а размер дискового кэша — как фиксированный объём в байтах.
Единичный загрузчик предотвращает создание дублирующихся кэшей и напрасное расходование памяти.
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()Предоставление единого загрузчика
Coil 3 позволяет задать один загрузчик для всего приложения, реализовав SingletonImageLoader.Factory в классе Application. После этого каждый AsyncImage использует его автоматически, и передавать загрузчик при каждом вызове не нужно.
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()
}Управление политиками кэширования
Для каждого запроса можно включить или отключить кэширование с помощью memoryCachePolicy и diskCachePolicy. Доступны политики ENABLED, READ_ONLY, WRITE_ONLY и DISABLED.
Например, отключите кэш в памяти для больших одноразовых изображений, которые не нужно хранить в RAM, или отключите дисковый кэш для конфиденциального содержимого.
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()Изменение размера: не декодируйте огромные растровые изображения
Самая серьёзная ошибка производительности — декодировать фотографию размером 4000x3000 в миниатюру размером 100dp. Это напрасно расходует память и время. По умолчанию Coil подбирает размер растрового изображения по измеренным границам компонуемого элемента, поэтому небольшой AsyncImage декодирует небольшое изображение.
Если размеры известны заранее, можно принудительно задать их с помощью size() в запросе.
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()Стабильные ключи кэша
Coil создаёт ключ кэша на основе данных запроса. Если URL содержит изменяющиеся параметры запроса (например, подписанный токен), одинаковые изображения получают разные ключи и никогда не находятся в кэше.
Задайте явные memoryCacheKey и diskCacheKey, используя стабильную часть данных, например идентификатор изображения, чтобы кэширование работало корректно.
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()Предварительная загрузка изображений
Для более плавной работы можно заранее прогреть кэш, прежде чем изображение будет показано, например предварительно загрузить следующую страницу ленты. Используйте enqueue загрузчика с запросом без целевого размера.
Когда пользователь дойдёт до изображения, оно уже будет в кэше и появится мгновенно.
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)
}
}Очистка кэша
Иногда нужно освободить место или принудительно обновить данные, например после того, как пользователь сменил аватар. Можно очистить кэш в памяти, дисковый кэш или удалить отдельный ключ.
Полная очистка требует много ресурсов, поэтому по возможности удаляйте только нужные ключи.
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
}Плавное появление и попадание в кэш
Плавное появление отлично подходит для загрузки по сети, но может казаться медленным, когда изображение сразу берётся из кэша в памяти и могло бы появиться мгновенно. Coil учитывает это: по умолчанию анимация плавного появления пропускается при попадании в кэш памяти и применяется только к действительно загруженным изображениям.
В результате при первой загрузке вы получаете плавное появление, а при прокручивании уже кэшированных изображений — мгновенное отображение без дополнительных действий.
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()Проверка производительности списков
При отображении множества изображений в LazyColumn или сетке соблюдайте следующие правила для плавной прокрутки:
- Задавайте каждому изображению фиксированный размер, чтобы Coil декодировал небольшое растровое изображение.
- Повторно используйте один единичный
ImageLoader, чтобы кэши были общими. - Используйте
ContentScale.Crop, чтобы избежать скачков макета. - Сохраняйте ключи кэша неизменными при повторных загрузках.
- Позволяйте Coil автоматически отменять запросы для элементов за пределами экрана — не мешайте ему.
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)
)
}
}
}Быстрая проверка
Coil проверяет свои кэши перед обращением к сети. Какой кэш при попадании работает быстрее всего, поскольку полностью пропускает декодирование?
Итоги
Вы узнали, как Coil ускоряет работу с изображениями:
- Два кэша: в памяти (декодированный, быстрый, небольшой) и на диске (исходные байты, сохраняется, большего объёма).
- Настраивайте размеры в пользовательском
ImageLoaderи предоставляйте один загрузчик черезSingletonImageLoader.Factory. - Настраивайте поведение каждого запроса с помощью
memoryCachePolicyиdiskCachePolicy. - Всегда декодируйте изображение в размере отображения; при необходимости задавайте явный
size(). - Используйте стабильные ключи кэша, заранее загружайте будущие изображения и удаляйте отдельные ключи для обновления данных.
Далее: перейдём от изображений к воспроизведению аудио и видео.
Часто задаваемые вопросы
Урок «Кэширование и производительность» бесплатный?
Да — полный текст урока «Кэширование и производительность» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.
Чему я научусь в уроке «Кэширование и производительность»?
Основы кэширования в памяти и на диске Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Android Academy?
Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Кэширование и производительность»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Android Academy?
Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Загрузка изображений с Coil
- Заполнители и состояния ошибок
- Кэширование и производительность
- Воспроизведение аудио и видео