0Pricing
Android Academy · Урок

Кэширование и производительность

Основы кэширования в памяти и на диске

«Кэширование и производительность» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Загрузка изображений с Coil
  2. Заполнители и состояния ошибок
  3. Кэширование и производительность
  4. Воспроизведение аудио и видео
← Назад к Android Academy