Kotlin协程retryIO失败捕获异常、重试次数不生效问题排查
问题根因
你遇到的两个问题本质是两个逻辑缺陷叠加导致的:
- 定时器逻辑导致重试任务重叠:你用的
java.util.Timer是固定10秒触发一次任务,完全不感知协程执行状态,也不管上一次健康检查的重试流程有没有跑完。结合你接口10秒左右的超时配置,每个定时器触发时,上一次启动的重试任务刚跑完第一次接口调用(超时失败打retryIO repeat 0 failed日志),新的重试任务就已经启动了。你日志里大量的repeat 0记录根本不是同一个重试任务的多次尝试,是每10秒新开的重试任务各自打出来的第一次失败日志,旧任务要么被后续逻辑打断,要么因为协程没有绑定生命周期被回收,根本跑不完3次重试流程,自然不会打印retryIO run final block日志。 - 异常捕获范围不全:
retryIO方法内仅捕获IOException,但实际网络请求场景下可能抛出非IO类型的异常(比如协程取消异常、序列化异常、部分框架封装的网络异常等),这类异常不会被重试块内的catch捕获,会直接中断重试流程;同时你每次调用都新建独立的CoroutineScope,没有配置全局异常处理器,未捕获的异常不会走到你写的外层catch逻辑,自然无法触发断连状态的UI更新。
修复方案
按以下步骤调整即可完全符合预期:
- 优化
retryIO方法,扩大异常捕获范围,支持自定义可重试异常判断,避免异常漏抓 - 废弃
java.util.Timer,改用协程原生的顺序轮询逻辑,保证上一次健康检查(含完整重试流程)执行完毕后,再等待间隔发起下一次检查,从根源上避免任务重叠 - 使用和页面生命周期绑定的协程域(如
lifecycleScope)启动任务,避免内存泄漏,保证页面销毁时轮询自动终止
修复后的retryIO实现
suspend fun <T> retryIO( times: Int = 3, initialDelay: Long = 100, // 0.1秒 maxDelay: Long = 1000, // 1秒 factor: Double = 2.0, retryOn: (Throwable) -> Boolean = { it is IOException }, // 可自定义需要重试的异常规则 block: suspend () -> T ): T { var currentDelay = initialDelay repeat(times - 1) { attempt -> try { return block() } catch (e: Throwable) { // 不可重试的异常直接抛出,终止重试 if (!retryOn(e)) throw e Timber.i("retryIO 第${attempt + 1}次调用失败,${currentDelay}ms后重试") Timber.e(e) } delay(currentDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) } Timber.i("retryIO 执行最后一次尝试") // 最后一次尝试不捕获异常,失败直接向外抛出 return block() }
修复后的健康检查轮询逻辑
// 用页面生命周期绑定的lifecycleScope启动,页面销毁时自动取消轮询,无需手动处理 lifecycleScope.launch { var isFirstRequest = true while (isActive) { if (!isFirstRequest) delay(10000L) // 非首次请求,等待10秒轮询间隔 isFirstRequest = false try { // 切换到IO线程执行网络请求 val result = withContext(Dispatchers.IO) { retryIO(times = 3) { api.doGetHealth().await() } } Timber.i("健康检查成功") // 切换到主线程更新UI withContext(Dispatchers.Main) { updateHealthStatus(HealthStatus.CONNECTED) } } catch (e: Exception) { Timber.i("健康检查重试全部失败") Timber.e(e) withContext(Dispatchers.Main) { updateHealthStatus(HealthStatus.DISCONNECTED) } } } }
修复效果说明
- 不会再出现大量重复的
repeat 0日志:轮询逻辑是顺序执行的,同一时间只会有一个健康检查任务在跑,重试次数严格按照传入的times参数执行 - 可以正常打印最后一次尝试的日志:没有重叠任务打断,重试流程可以完整执行
- 异常可以被正常捕获:所有重试失败后抛出的异常会被外层catch块捕获,正常触发断连状态的UI更新
- 无内存泄漏风险:协程和页面生命周期绑定,页面退出后轮询自动终止,不会产生孤儿协程占用资源
内容的提问来源于stack exchange,提问作者chancyWu
相关产品推荐
相关产品推荐

