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

Ktor Client无法捕获ConnectException,求异常拦截方案

解决Ktor Client无法捕获java.net.ConnectException的方案

你的问题核心在于协程环境下未正确处理跨线程抛出的异常,以及原代码中runCatching { ... }.getOrElse { throw it }的写法等于直接重新抛出异常,没有起到捕获作用。以下是几种可行的异常拦截方案:

方案一:在协程作用域内添加全局异常处理器

为协程指定CoroutineExceptionHandler,可以捕获所有协程内未被局部try-catch处理的异常,包括ConnectException:

// 定义全局异常处理器
val requestExceptionHandler = CoroutineExceptionHandler { _, throwable ->
    when (throwable) {
        is java.net.ConnectException -> {
            // 处理连接异常,比如更新UI提示用户网络问题
        }
        is RedirectResponseException, is ClientRequestException, is ServerResponseException -> {
            // 处理HTTP状态码异常
        }
        else -> {
            // 处理其他未知异常
        }
    }
}

// 在带异常处理器的协程中发起请求
CoroutineScope(Dispatchers.IO + requestExceptionHandler).launch {
    try {
        val response = client.post("$BASE_URL/api/users/login")
        val result = response.body()
        // 处理成功响应
    } catch (e: RedirectResponseException) {
        Result.Error(e, e.response.status.description)
    } catch (e: ClientRequestException) {
        Result.Error(e, e.response.status.description)
    } catch (e: ServerResponseException) {
        Result.Error(e, e.response.status.description)
    }
    // 注意:ConnectException会被全局处理器捕获,无需在这里额外catch
}

方案二:通过HttpClient拦截器统一捕获发送阶段异常

在HttpClient初始化时安装HttpSend拦截器,在请求发送环节直接捕获ConnectException:

val client = HttpClient(Android) {
    install(Logging) {
        level = LogLevel.ALL
    }
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
        })
    }
    // 安装HttpSend拦截器,捕获请求发送时的异常
    install(HttpSend) {
        intercept { request ->
            runCatching {
                execute(request)
            }.getOrElse { throwable ->
                when (throwable) {
                    is java.net.ConnectException -> {
                        // 可以在这里直接处理异常,或者重新抛出让上层协程捕获
                        // 例如返回一个自定义错误响应:
                        HttpResponse(request, HttpStatusCode.ServiceUnavailable, headersOf(), ByteReadChannel.Empty)
                        // 或者重新抛出:throw throwable
                    }
                    else -> throw throwable
                }
            }
        }
    }
}

方案三:用runCatching直接包裹请求并处理结果

移除原代码中无效的runCatching + getOrElse组合,直接用runCatching处理整个请求流程:

CoroutineScope(Dispatchers.IO).launch {
    val result = runCatching {
        client.post("$BASE_URL/api/users/login")
    }.fold(
        onSuccess = { response ->
            Result.Success(response.body())
        },
        onFailure = { throwable ->
            when (throwable) {
                is RedirectResponseException -> Result.Error(throwable, throwable.response.status.description)
                is ClientRequestException -> Result.Error(throwable, throwable.response.status.description)
                is ServerResponseException -> Result.Error(throwable, throwable.response.status.description)
                is java.net.ConnectException -> Result.Error(throwable, throwable.message.orEmpty())
                else -> Result.Error(throwable, throwable.message.orEmpty())
            }
        }
    )
    // 处理最终的Result对象
}

关键注意事项

  1. 必须在协程内执行Ktor请求:Ktor的post等方法是挂起函数,必须在协程作用域(如CoroutineScope、viewModelScope等)内调用,否则会导致异常直接抛出无法捕获。
  2. 避免无效的异常重抛:原代码中runCatching { ... }.getOrElse { throw it }会把捕获到的异常重新抛出,等同于没处理,直接删除这个逻辑即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:51:28