Как исправить ошибку "Inappropriate blocking method call" в OkHttp и корутинах Kotlin?
Почему возникает ошибка и как её исправить
Ошибка "Inappropriate blocking method call" возникает, потому что метод OkHttpClient.newCall().execute() является блокирующим — он останавливает текущий поток до получения ответа. В корутинах это нарушает принцип неблокирующего выполнения: поток должен быть свободен для других корутин, а не ждать I/O.
Есть два правильных подхода:
1. Использование suspendCancellableCoroutine (рекомендуется)
Этот подход превращает колбэк-стиль OkHttp в корутину:
import kotlinx.coroutines.*
import okhttp3.*
import java.io.IOException
suspend fun fetchData(): String = suspendCancellableCoroutine { continuation ->
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
val call = client.newCall(request)
// Обработка отмены корутины
continuation.invokeOnCancellation {
call.cancel()
}
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
override fun onResponse(call: Call, response: Response) {
if (continuation.isActive) {
val body = response.body?.string() ?: ""
continuation.resume(body)
}
}
})
}
Как это работает:
suspendCancellableCoroutineприостанавливает корутину и предоставляет объектContinuation.- Внутри мы вызываем
enqueue()— асинхронный метод OkHttp, который выполняется в фоновом потоке. - Когда ответ получен, мы вызываем
continuation.resume(), чтобы возобновить корутину. - При отмене корутины вызовется
invokeOnCancellation, который отменит HTTP-запрос. - Важно проверять
continuation.isActiveперед resume, чтобы избежать повторного возобновления.
2. Использование корутинового Dispatchers.IO
Если вы уверены, что блокирующий вызов короткий, можно обернуть его в withContext(Dispatchers.IO):
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
val response = client.newCall(request).execute()
response.body?.string() ?: ""
}
Этот способ проще, но менее эффективен, так как занимает поток из пула IO. Он подходит для простых случаев, но не рекомендуется для высоконагруженных приложений.
Дополнительно: работа с Moshi
После получения JSON строки вы можете распарсить её с помощью Moshi:
data class ApiResponse(val data: String)
suspend fun fetchAndParse(): ApiResponse {
val json = fetchData() // из примера выше
val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(ApiResponse::class.java)
return adapter.fromJson(json) ?: throw RuntimeException("Failed to parse")
}
Лучшая практика: используйте первый подход с suspendCancellableCoroutine — он полностью корутино-совместим, поддерживает отмену и не блокирует потоки. В продакшене также стоит настроить OkHttpClient с таймаутами и пулом соединений.
Альтернатива: использование библиотеки Ktor Client
Если вы не привязаны к OkHttp, рассмотрите Ktor Client — он изначально спроектирован для корутин и не требует обёрток:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
suspend fun fetchData(): String {
val client = HttpClient()
val response: HttpResponse = client.get("https://api.example.com/data")
return response.bodyAsText()
}
Преимущества:
- Нет блокирующих вызовов — всё асинхронно на уровне корутин.
- Легко настраивается (сериализация, таймауты, перехватчики).
- Интеграция с Moshi через плагин
ktor-client-moshi.
Недостаток: нужно подключать новую библиотеку и менять код. Если проект уже использует OkHttp, лучше оставить первый подход.
Также можно использовать OkHttp с enqueue() и колбэками, но это менее удобно в корутинах — приходится вручную управлять Continuation.
Выбирайте подход под свои задачи: для нового проекта — Ktor, для существующего с OkHttp — suspendCancellableCoroutine.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги