В предыдущей статье я описал процесс реализации механизма кэширования новостных тем в приложении TrendNow с помощью базы данных Room. Если вы пропустили эту статью, можете ознакомиться с ней здесь:
Реализация кэширования новостных тем в приложении TrendNow. Часть 5
Эта статья посвящена продолжению решения проблемы ограничения скорости API путем реализации механизма кэширования трендовых новостей.
Напомню, что исходный код в репозитории Github может отличаться от приведенных здесь фрагментов кода, поскольку я работал над ним до написания этой статьи.
Что я собираюсь сделать?
- Использовать класс Cache библиотеки
OkHttpдля сохранения ответа на трендовые новости.
- Обеспечить загрузку в приложение трендовых новостей из кэшированного ответа
OkHttp, когда он будет доступен.
Почему для кэширования новостных тем подходит Room, а для кэширования трендовых новостей — OkHttp Cache?
- Я использовал
Roomдля кэширования новостных тем (о чем рассказал в предыдущей статье), чтобы сохранить ответ в течение длительного периода времени. В данном случае использованиеRoomуместней по сравнению сOkHttpCache, который удаляет кэш при заполнении хранилища кэша.
- Трендовые новости буду кэшировать только в течение короткого периода. Поэтому достаточно использовать
OkHttpCache.
Активация OkHttp Cache
Сначала надо отредактировать клиент OkHttp в Hilt-модуле — NetworkModule.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val CACHE_DIR = "news_cache"
private const val CACHE_SIZE_IN_MB = 50L // 50MB
@Provides
@Singleton
fun provideOkHttpClient(
@ApplicationContext context: Context // inject application context
): OkHttpClient {
// определить путь каталога кэша
val file = File(context.filesDir, CACHE_DIR)
// установка каталога и размера кэша
val cache = Cache(file, CACHE_SIZE_IN_MB * 1024 * 1024)
return OkHttpClient.Builder()
// установка OkHttp Cache
.cache(cache)
...
}
...
}
- Важное замечание: использование
context.cacheDirявляется достаточным для каталогаOkHttpCache, если у вас нет веских причин не использовать его.
context.filesDir: использование каталога files вместо каталога cache позволяет предотвратить очистку кэша новостей с помощью типичных сторонних приложений для чистки телефона.
После этого OkHttp должен начать создавать кэш ответа. Можно проверить каталог cache с помощью Android Studio Device Explorer.

- Каталог кэша должен находиться внутри каталога AppData
data/data/$com_package_name/$cache_dir. В данном случае это должно иметь такой вид:data/data/com.trend.now/files/news_cache.
- Как видно на приведенном выше изображении, внутри каталога
news_cacheесть несколько файлов — это кэш ответов, созданныйOkHtpp.
Все ли готово?
Нет, не все. Кэшированный ответ есть, но OkHtpp не будет его использовать, если сервер не поддерживает его.
Как определить, поддерживает ли сервер кэшированные ответы?
По заголовку ответа с Cache-Control. Проверим заголовок ответа, возвращенного с rapidapi.com, с помощью Android Studio Network Inspector.

- Как видите, в заголовке ответа (правое поле) отсутствует
Cache-Control.
Это означает, что кэш есть, но любой запрос, сделанный с помощью Retrofit, все равно обращается к серверу, чтобы получить сетевой ответ. Если сервер поддерживает эту функцию, то заголовок ответа должен выглядеть так, как показано на приведенном ниже изображении.

Когда в заголовке ответа есть Cache-Control, OkHttp автоматически использует кэш, если он еще действителен, и запрашивает из сети, если срок его действия истек.
Как заставить OkHttp использовать кэшированные ответы
В качестве обходного пути можно заставить OkHttp использовать кэш, когда он доступен.
Сначала надо изменить OkHttp Cache на синглтон:
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val CACHE_DIR = "news_cache"
private const val CACHE_SIZE_IN_MB = 50L // 50MB
@Provides
@Singleton
fun provideOkHttpClient(
cache: Cache // сюда - внедрение OkHttp Cache
): OkHttpClient {
return OkHttpClient.Builder()
// установка OkHttp Cache
.cache(cache)
...
}
@Provides
@Singleton
fun provideOkHttpCache(@ApplicationContext context: Context): Cache {
// определить путь каталога кэша
val file = File(context.filesDir, CACHE_DIR)
// установка каталога и размера кэша
return Cache(file, CACHE_SIZE_IN_MB * 1024 * 1024)
}
...
}
Теперь можно создать новый перехватчик (interceptor) OkHtpp для перехвата запроса.
class CacheInterceptor @Inject constructor(
private val cache: Cache // inject the okhttp cache
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val requestBuilder = request.newBuilder()
val url = request.url.toString()
// проверьте, доступен ли кэш ответов или нет
val responseCache = cache.urls().find { it == url }
val newRequest = if (responseCache != null) {
requestBuilder
// если он доступен, нужно заставить okhttp использовать кэшированный ответ
.cacheControl(CacheControl.FORCE_CACHE)
.build()
} else {
// Если кэш недоступен, действуйте как обычно
// обращение к серверу
requestBuilder.build()
}
return chain.proceed(newRequest)
}
}
- Проверяю, есть ли в
OkHttpкэшированный ответ от определенного url или нет, используяcache.urls(). Если кэш доступен — заставляюOkHttpиспользовать его с помощьюCacheControl.FORCE_CACHE. Если нет — запрашиваю сервер, как обычно.
cache.urls()возвращает объектMutableIterator<String>, поиск строки в нем имеет сложность O(n). Большое количество кэшей может повлиять на производительность приложения и увеличить сетевую задержку. Но это — единственное из доступных мне средств, поэтому я им воспользовался.
Перехватчик кэша создается в файле CacheInterceptor.kt в каталоге core/network/.
core/
├── network/
│ ├── CacheInterceptor.kt
│ ├── HeaderInterceptor.kt
Наконец, регистрирую перехватчик в OkHtpp внутри Hilt-модуля.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
...
@Provides
@Singleton
fun provideOkHttpClient(cache: Cache): OkHttpClient {
return OkHttpClient.Builder()
...
// здесь происходит регистрация перехватчика
.addInterceptor(CacheInterceptor(cache))
...
}
...
}
Проверка ответа Retrofit
После всех вышеописанных изменений Retrofit должен использовать кэшированный ответ, если он доступен. Чтобы проверить это, добавлю точку останова в NewsRemoteDataSource, когда приложение вызовет fetchTrendingNews() во второй раз.


При оценке response.raw() выясняется: cacheResponse не равен null, а networkResponse равен null. Это означает, что ответ получен из кэша.
Проблема решена?
Частично. В текущей реализации OkHtpp всегда использует кэш, когда он доступен, и никогда не отправит сетевой запрос, пока есть кэш. Я решил на время оставить эту реализацию с упомянутой выше проблемой.
Итоги реализации OkHttp Cache
- Реализация
OkHttpCache для трендовых новостей поможет минимизировать ограничение скорости API.
- Хотя кэш уже реализован, не все проблемы, связанные с ним, решены. Нужно еще определить, когда использовать кэш и делать сетевой запрос.
- Важное замечание: возможно, вам придется пересмотреть использование
OkHttpCache, если сервер возвращает ответ с конфиденциальной информацией. Насколько я знаю, нет способа заставитьOkHttpкэшировать только определенный url-ответ.
В следующей статье расскажу, как реализовал более эффективный механизм кэширования, объединив OkHttp Cache и базу данных Room, чтобы решить вышеуказанную проблему.
Ознакомьтесь с полным исходным кодом на Github.
Читайте также:
- Вопросы для собеседования по Android: как обрабатывать валидацию ввода в Jetpack Compose?
- 5 функций-расширений в арсенале каждого разработчика Jetpack Compose
- Как создать загрузчик с вращающимися кругами в Jetpack Compose
Читайте нас в Telegram, VK и Дзен
Перевод статьи Dani Mahardhika: Implement OkHttp Cache for Client-Side Caching





