Как правильно кешировать данные в Android‑приложениях

Коротко: кэш уменьшает задержки и сетевой трафик, но требует стратегии инвалидации: выбирайте уровень (память/диск/HTTP/БД) под тип данных, настраивайте TTL или event‑based обновление и собирайте метрики hit/miss для корректировки параметров.

Оглавление {{TOC_AUTOMATIC}}

Стратегии кэширования и когда их применять

  • Cache-aside (on‑demand): приложение читает из кэша, при промахе — из источника и кладёт в кэш. Хорош для редко изменяемых данных и сложных вычислений.
  • Write-through: запись одновременно в кэш и в основной источник — обеспечивает согласованность, но увеличивает задержку записи.
  • Write-back (write‑behind): быстрое локальное подтверждение, фоновой синхрон на сервер — подходит для UX‑чувствительных операций, но требует защиты от потери данных.
  • Read‑through / Cache‑through: кэш выступает фронтом для чтений — упрощает логику клиента, усложняет инвалидацию.

Инвалидация:

  • TTL (time‑based) — простая, подходит для предсказуемо устаревающих данных.
  • Event‑based — реагирует на изменения (обновление записи, logout, push).
  • Validation (ETag/Last‑Modified) — идеальна для HTTP‑ответов.

Где хранить кэш: преимущества и практические советы

  1. Память (in‑memory)
    • LruCache или библиотеки (Glide memory cache). Используйте для горячих объектов (bitmap, сессии).
    • Живёт пока процесс жив — не рассчитывайте на сохранность при убийстве процесса.
  2. Диск (on‑disk)
    • DiskLruCache / cacheDir для больших бинарников и API‑ответов.
    • Чтение/запись в фоне; учитывайте I/O и декодинг bitmap.
  3. HTTP‑кэш (OkHttp)
    • Конфигурируется через Cache и корректные HTTP‑заголовки. Поддерживает validation (ETag).
    • Один shared OkHttpClient на приложение — критично.
  4. Локальная БД / Key‑Value
    • Room для структурированных данных с запросами; DataStore/SharedPreferences для метаданных и timestamp.
  5. Специально для изображений
    • Glide/Coil/Fresco: многослойные кэши (active → memory → disk). Не изобретайте wheel — используйте проверенные решения.

Подберите размер disk/http cache эмпирически (часто 10–100 MB для HTTP, для image cache — настраиваемые лимиты). Начните с консервативных значений и корректируйте по метрикам.

Практические примеры (Kotlin)

OkHttp: подключение disk cache

val cacheSize = 50L * 1024L * 1024L // 50 MB
val cacheDir = File(context.cacheDir, "http_cache")
val cache = Cache(cacheDir, cacheSize)

val okHttpClient = OkHttpClient.Builder()
    .cache(cache)
    .build()

Glide: корректная очистка

// main thread
Glide.get(context).clearMemory()
// background
Thread { Glide.get(context).clearDiskCache() }.start()

Простой LruCache для bitmap

val cacheSizeKb = (Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()
val memoryCache = object : LruCache<String, Bitmap>(cacheSizeKb) {
    override fun sizeOf(key: String, value: Bitmap) = value.byteCount / 1024
}

Не очищайте кэш при каждом запуске приложения — это разрушает выгоду кэша и увеличивает сетевой трафик.

Управление жизненным циклом и очистка

  • Eviction: LRU — стандартный выбор, disk LRU удаляет старые элементы при переполнении.
  • Принудительная очистка: cache.evictAll() (OkHttp), Glide.clearMemory()/clearDiskCache().
  • Когда вызывать очистку: logout (удаление персональных данных), ручное действие пользователя, аварийная очистка при повреждении данных.
  • Реагируйте на системные сигналы низкого места и позвольте LRU механизму освобождать место.

Мониторинг и метрики

Собирайте: cache hit rate, miss rate, evictions, current size, average TTL expirations. Для OkHttp используйте EventListener/логирование запросов, для своих кэшей — инкрементные счётчики и метрики в APM.

Частые ошибки

  • Нет инвалидации → устаревшие данные. Решение: TTL + event‑based.
  • Несколько экземпляров OkHttp/Glide → конфликты и потеря кэша. Решение: единая конфигурация.
  • Блокировка UI при дисковых операциях — всегда в фоне.
  • Очистка кэша как "фикс" без анализа причин — ищите корень (утечки, декодинг).

FAQ

  • Какой TTL ставить? — Зависит от данных: аватарки и статика — долгие TTL; финансовые операции — короткие или event‑based инвалидация.
  • Где хранить JSON‑ответы? — Для структурированных данных лучше Room; для кэширования HTTP‑ответов — OkHttp disk cache + validation.
  • Можно ли комбинировать стратегии? — Да. Часто используют in‑memory + disk + HTTP validation.

Чек‑лист перед продакшн‑внедрением

  • [ ] Категоризовать данные по свежести.
  • [ ] Назначить уровень хранения для каждой категории.
  • [ ] Реализовать TTL и/или event‑based invalidation.
  • [ ] Настроить мониторинг (hit/miss, evictions, size).
  • [ ] Предусмотреть безопасные API для принудительного обновления.
  • [ ] Протестировать поведение при ограниченной памяти и перезапуске.

Заключение: проектируйте кэш как часть архитектуры — отдельные политики для разных типов данных, мониторинг и возможность безопасной инвалидации важнее максимального размера кэша. Если нужно — помогу подобрать конфигурацию и пример кода под конкретный кейс (аватарки, feed, offline shop).