Как правильно кешировать данные в 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‑ответов.
Где хранить кэш: преимущества и практические советы
- Память (in‑memory)
- LruCache или библиотеки (Glide memory cache). Используйте для горячих объектов (bitmap, сессии).
- Живёт пока процесс жив — не рассчитывайте на сохранность при убийстве процесса.
- Диск (on‑disk)
- DiskLruCache / cacheDir для больших бинарников и API‑ответов.
- Чтение/запись в фоне; учитывайте I/O и декодинг bitmap.
- HTTP‑кэш (OkHttp)
- Конфигурируется через Cache и корректные HTTP‑заголовки. Поддерживает validation (ETag).
- Один shared OkHttpClient на приложение — критично.
- Локальная БД / Key‑Value
- Room для структурированных данных с запросами; DataStore/SharedPreferences для метаданных и timestamp.
- Специально для изображений
- 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).