Как исправить ошибку "inappropriate blocking method call" в Kotlin Coroutines с OkHttp?
Правильный способ: использование Dispatchers.IO и suppression аннотации
Проблема в том, что вы вызываете блокирующий метод execute() внутри корутины, которая по умолчанию работает на Dispatchers.Main (или другом не-блокирующем диспатчере). IDE предупреждает, что это может заблокировать поток корутины. Решение — явно переключиться на Dispatchers.IO, предназначенный для блокирующих операций, и подавить предупреждение с помощью аннотации @Suppress("BlockingMethodInNonBlockingContext").
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
suspend fun fetchData(): String {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
return withContext(Dispatchers.IO) {
@Suppress("BlockingMethodInNonBlockingContext")
val response = client.newCall(request).execute()
response.body?.string() ?: ""
}
}
Почему это работает?
- Dispatchers.IO использует пул потоков, оптимизированных для блокирующих операций (например, сетевых запросов или чтения файлов).
- withContext временно переключает контекст корутины на указанный диспетчер, выполняя блок кода в подходящем потоке.
- После завершения корутина автоматически возвращается на исходный диспетчер (например, Main для UI).
- Аннотация
@Suppressубирает предупреждение IDE, так как вы осознанно используете блокирующий метод в правильном контексте.
Альтернатива: использование enqueue() с корутинами
Если вы хотите избежать блокировки совсем, можно использовать асинхронный метод enqueue() и преобразовать его в корутину с помощью suspendCancellableCoroutine:
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Response
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun fetchDataAsync(): String {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
return suspendCancellableCoroutine { continuation ->
val call = client.newCall(request)
continuation.invokeOnCancellation { call.cancel() }
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
continuation.resume(response.body?.string() ?: "")
}
})
}
}
Какой подход выбрать?
- Первый вариант (withContext + @Suppress) — проще и короче. Подходит для большинства случаев, особенно если вы не делаете тысячи параллельных запросов.
- Второй вариант (suspendCancellableCoroutine) — чисто асинхронный, без блокировки потоков. Лучше для высоконагруженных систем или когда нужно отменять запросы при отмене корутины.
- Третий вариант — использовать специализированную библиотеку, например Ktor или Fuel, которые изначально поддерживают корутины.
В вашем случае с OkHttp и Moshi рекомендую первый вариант, так как он минимально меняет код и решает проблему. Главное — не забывайте про Dispatchers.IO, иначе вы рискуете заблокировать главный поток в Android-приложении.
Использование Ktor Client как альтернатива OkHttp
Если вы готовы сменить HTTP-клиент, рассмотрите Ktor Client — он изначально спроектирован для Kotlin Coroutines и не имеет проблем с блокирующими вызовами. Вот пример интеграции с Moshi:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
// Для парсинга JSON используем kotlinx.serialization (аналог Moshi)
@Serializable
data class MyData(val id: Int, val name: String)
suspend fun fetchDataWithKtor(): MyData {
val client = HttpClient()
val response: HttpStatement = client.get("https://api.example.com/data")
val jsonString = response.bodyAsText()
return Json.decodeFromString(jsonString)
}
Почему это лучше?
- Нет блокирующих методов — все запросы асинхронны по умолчанию.
- Не нужно использовать
Dispatchers.IOили подавлять предупреждения. - Ktor поддерживает плагины для сжатия, логирования, сериализации и т.д.
- Легко интегрируется с Moshi через дополнительный модуль
ktor-client-moshi(если не хотите переходить на kotlinx.serialization).
Недостаток: нужно переписывать весь код HTTP-запросов. Но если вы только начинаете проект или готовы к рефакторингу, это избавит вас от головной боли с блокирующими вызовами навсегда.