You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中Ktor Client获HTTP 200响应仍抛出IOException问题

Android端Ktor Client调用接口抛出EOFException问题

问题描述

Android端调用接口时,网络 inspector显示接口返回HTTP 200状态码,设备也收到了预期的推送通知,但Ktor Client却抛出IOException,具体为java.io.EOFException,日志显示代码执行到POINT 1后触发异常。

相关代码

return flow {
  try {
    Timber.e("POINT, 1")

    val response: HttpResponse = httpClient.post(url) {
      setBody(enrolmentRequestData)
    }
    Timber.e("POINT, 1 2, ${response.status.value}")
    Timber.e("POINT, 1 2, Response body: ${response.bodyAsText()}") // 尝试打印响应体

    if (response.status == HttpStatusCode.OK) {
      emit(Result.Success(Unit))
    } else {
      handleErrorResponse(response.status.value)
    }
  } catch (e: ResponseException) {
    Timber.e("ResponseException: ${e.response.status.value}, ${e.message}")
    handleErrorResponse(e.response.status.value)
  } catch (e: JSONException) {
    Timber.e("JSONException: ${e.message}")
    emit(Result.Error(DataError.Network.JSON_PARSE_EXCEPTION))
  } catch (e: IOException) {
    Timber.e("IOException: ${e.message}, ${e.stackTrace}}")
    e.printStackTrace() // 打印完整堆栈便于分析
    emit(Result.Error(DataError.Network.NO_INTERNET))
  } catch (e: Exception) {
    Timber.e("Unknown Exception: ${e.message}, ${e.stackTrace}")
    emit(Result.Error(DataError.Network.UNKNOWN_ERROR))
  }
}

日志信息

E  IOException: null, [Ljava.lang.StackTraceElement;@15c8851}
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  java.io.EOFException
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at java.lang.reflect.Constructor.newInstance0(Native Method)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ExceptionUtilsJvmKt$createConstructor$$inlined$safeCtor$3.invoke(SourceFile:81)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ExceptionUtilsJvmKt$createConstructor$$inlined$safeCtor$3.invoke(SourceFile:90)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ExceptionUtilsJvmKt.tryCopyException(SourceFile:66)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ByteBufferChannelKt.rethrowClosed(SourceFile:2404)
2024-06-28 22:15:39.704 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ByteBufferChannelKt.access$rethrowClosed(SourceFile:1)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ByteBufferChannel.readRemaining$suspendImpl(SourceFile:2063)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ByteBufferChannel.readRemaining(Unknown Source:0)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.utils.io.ByteReadChannel$DefaultImpls.readRemaining$default(SourceFile:88)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.call.SavedCallKt.save(SourceFile:73)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.statement.HttpStatement$execute$4.invokeSuspend(SourceFile:63)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.statement.HttpStatement$execute$4.invoke(SourceFile:0)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.statement.HttpStatement$execute$4.invoke(SourceFile:0)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.statement.HttpStatement.execute(SourceFile:50)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.client.statement.HttpStatement$execute$1.invokeSuspend(Unknown Source:12)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:33)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlinx.coroutines.DispatchedTask.run(SourceFile:102)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlinx.coroutines.EventLoop.processUnconfinedEvent(SourceFile:65)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(SourceFile:199)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SourceFile:135)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.util.pipeline.SuspendFunctionGun.loop(SourceFile:109)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SourceFile:11)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SourceFile:59)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:46)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at kotlinx.coroutines.DispatchedTask.run(SourceFile:102)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at android.os.Handler.handleCallback(Handler.java:959)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at android.os.Handler.dispatchMessage(Handler.java:100)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at android.os.Looper.loopOnce(Looper.java:232)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at android.os.Looper.loop(Looper.java:317)
2024-06-28 22:15:39.705 24066-24066 System.err  eu.europa.ec.ecas W  at android.app.ActivityThread.main(ActivityThread.java:8592)
2024-06-28 22:15:39.706 24066-24066 System.err  eu.europa.ec.ecas W  at java.lang.reflect.Method.invoke(Native Method)
2024-06-28 22:15:39.706 24066-24066 System.err  eu.europa.ec.ecas W  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
2024-06-28 22:15:39.706 24066-24066 System.err  eu.europa.ec.ecas W  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)

问题原因分析

从堆栈日志看,异常发生在Ktor的SavedCallKt.save环节,读取响应体时触发EOFException,可能的原因包括:

  • 服务器返回200后提前关闭连接,导致Ktor读取响应体时没有更多数据
  • 服务器返回的Content-Length头部与实际响应体长度不匹配,Ktor预期读取更多数据但已到结尾
  • Ktor客户端默认启用了响应保存功能,在保存响应过程中读取空响应体或不完整响应体时触发异常

解决方案

  1. 单独捕获EOFException并正确处理
    因为实际请求已经成功(收到推送、返回200),不应将此异常归为无网络错误,而是视为请求成功:

    catch (e: EOFException) {
        Timber.e("EOFException: 响应体读取完成,请求实际已成功")
        emit(Result.Success(Unit))
    } catch (e: IOException) {
        Timber.e("IOException: ${e.message}, ${e.stackTrace}}")
        e.printStackTrace()
        emit(Result.Error(DataError.Network.NO_INTERNET))
    }
    
  2. 跳过响应体读取(如果不需要响应内容)
    在获取响应后直接调用discardContent(),避免读取响应体触发异常:

    val response: HttpResponse = httpClient.post(url) {
      setBody(enrolmentRequestData)
    }
    response.discardContent() // 丢弃响应体,避免读取操作
    
  3. 禁用Ktor的SavedCall功能
    如果不需要保存请求响应记录,可以在客户端配置中关闭此功能:

    val httpClient = HttpClient(OkHttp) {
        install(HttpSend) {
            saveCallToHistory = false
        }
    }
    
  4. 检查服务器响应头
    确认服务器返回的Content-Length是否准确,或者使用分块传输编码(Transfer-Encoding: chunked)避免长度不匹配问题。

内容的提问来源于stack exchange,提问作者Rob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:27:33