Кэширование данных в Kotlin: от простого к профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Кэширование данных в Kotlin: как ускорить приложение в 10 раз без магии

Представьте: вы открываете любимое приложение, а оно грузится 5 секунд, показывая «заглушку» и бесконечный спиннер. Знакомо? А теперь представьте, что та же самая программа открывается мгновенно, даже без интернета. Секрет — в грамотном кэшировании данных. И сегодня мы разберем, как реализовать его в Kotlin так, чтобы ваш код был не только быстрым, но и элегантным.

Kotlin — язык, который обожают за лаконичность и безопасность. Но даже на самом крутом языке можно написать тормозное приложение, если не думать о кэшировании. В этой статье мы пройдем путь от простого in-memory кэша до продвинутых решений с Redis и DiskLruCache. Вы узнаете, как кэшировать данные в Kotlin так, чтобы пользователи забыли о загрузках, а сервер — о лишних запросах.

Зачем вообще кэшировать данные в Kotlin?

Кэширование — это как хорошая привычка: сначала сложно, потом незаменимо. Вот три причины, почему вашему приложению на Kotlin нужен кэш:

  • Скорость: данные из памяти читаются за микросекунды, а не за секунды, как с сервера или диска.
  • Экономия трафика: меньше запросов к API — меньше денег на сервера и мобильный интернет.
  • Оффлайн-режим: пользователи любят, когда приложение работает даже в метро без связи.

Но есть нюанс: неправильное кэширование может привести к утечкам памяти (OutOfMemoryError — привет из прошлого) или к показу устаревших данных. Поэтому давайте разбираться, как делать это правильно.

In-memory кэш: самый простой способ ускорить Kotlin приложение

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

Самый простой вариант — использовать HashMap или ConcurrentHashMap (если работаете с многопоточностью). Но в Kotlin мы можем сделать это красивее:

class SimpleCache<K, V>(
    private val maxSize: Int = 100
) {
    private val cache = LinkedHashMap<K, V>(0, 0.75f, true) // access-order

    fun get(key: K): V? {
        return cache[key]
    }

    fun put(key: K, value: V) {
        if (cache.size >= maxSize) {
            val oldest = cache.keys.firstOrNull()
            oldest?.let { cache.remove(it) }
        }
        cache[key] = value
    }

    fun clear() {
        cache.clear()
    }
}

Этот кэш автоматически удаляет самый старый элемент, когда достигает лимита. Но есть проблема — он не учитывает время жизни данных. Допустим, вы кэшировали список курсов валют, а через час курс изменился, а пользователь видит старые данные. Нехорошо.

Добавляем TTL (Time To Live)

data class CacheEntry<T>(
    val data: T,
    val expirationTime: Long
)

class TTLCache<K, V>(
    private val ttlMillis: Long = 60_000, // 1 минута
    private val maxSize: Int = 100
) {
    private val cache = LinkedHashMap<K, CacheEntry<V>>(0, 0.75f, true)

    fun get(key: K): V? {
        val entry = cache[key] ?: return null
        if (System.currentTimeMillis() > entry.expirationTime) {
            cache.remove(key)
            return null
        }
        return entry.data
    }

    fun put(key: K, value: V) {
        if (cache.size >= maxSize) {
            val oldest = cache.keys.firstOrNull()
            oldest?.let { cache.remove(it) }
        }
        cache[key] = CacheEntry(value, System.currentTimeMillis() + ttlMillis)
    }
}

Теперь данные живут ровно столько, сколько нужно. Идеально для кэширования ответов API, где данные меняются нечасто (например, список категорий товаров).

Кэширование в Android: DiskLruCache и Room

На мобильных устройствах память — ресурс ограниченный. Если ваше Android-приложение на Kotlin будет хранить всё в RAM, система убьет его при нехватке памяти. Решение — кэшировать на диск.

DiskLruCache — это библиотека от Google, которая позволяет хранить данные в файловой системе с контролем размера. Работает она просто:

class DiskCache(private val cacheDir: File, private val maxSize: Long = 10 * 1024 * 1024) {
    private val cache = DiskLruCache.open(cacheDir, 1, 1, maxSize)

    fun put(key: String, data: String) {
        val editor = cache.edit(key.md5()) ?: return
        try {
            editor.set(0, data)
            editor.commit()
        } catch (e: IOException) {
            editor.abort()
        }
    }

    fun get(key: String): String? {
        val snapshot = cache.get(key.md5()) ?: return null
        return snapshot.getString(0)
    }

    private fun String.md5(): String {
        val digest = MessageDigest.getInstance("MD5").digest(toByteArray())
        return digest.joinToString("") { "%02x".format(it) }
    }
}

Обратите внимание: ключи кэша нужно хэшировать (MD5 или SHA-1), чтобы избежать проблем с именами файлов. DiskLruCache сам следит за размером — когда превышаете лимит, он удаляет самые старые записи.

Room — это вообще отдельная песня. Если вы используете его как базу данных, вы уже кэшируете данные на диск. Но можно сделать гибрид: последние данные держать в памяти, а остальные — в Room. Пример:

class HybridCache<T>(
    private val memoryCache: TTLCache<String, T>,
    private val diskCache: RoomDao
) {
    suspend fun get(key: String): T? {
        // 1. Пробуем из памяти
        memoryCache.get(key)?.let { return it }

        // 2. Пробуем с диска
        val fromDisk = diskCache.get(key)
        if (fromDisk != null) {
            memoryCache.put(key, fromDisk) // обратно в память
            return fromDisk
        }

        return null
    }
}

Такой подход используется в 90% production-приложений. Быстро, надежно, оффлайн-ready.

Кэширование запросов к API: Retrofit + OkHttp

Если ваше приложение на Kotlin общается с сервером через Retrofit, вы можете кэшировать ответы прямо на уровне HTTP-клиента. OkHttp (который под капотом у Retrofit) поддерживает кэширование из коробки. Нужно только настроить:

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

val okHttpClient = OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor { chain ->
        val request = chain.request()
        val response = chain.proceed(request)

        // Кэшируем только GET-запросы на 5 минут
        if (request.method == "GET") {
            response.newBuilder()
                .header("Cache-Control", "public, max-age=                

Блоги

Рекомендаций по книгам