OkHttp 重试机制基础

在OkHttp的设计哲学中,网络恢复能力是传输层的内建能力。默认情况下,OkHttp会在特定条件下自动重试失败的请求,减少因瞬时网络抖动导致的调用失败。理解该机制的默认行为、控制开关以及精确的触发条件,是后续在Retrofit层实现自定义重试策略的前提。

学习目标:掌握OkHttp默认重试行为、retryOnConnectionFailure配置的作用范围、区分可重试与不可重试的失败场景,为后续覆盖Retrofit拦截器重试、合并延迟重试做好准备。

4.1 默认重试行为

OkHttpClient在构建时不显式配置任何重试相关参数时,其默认行为如下:

  • 启用状态:默认开启重试(retryOnConnectionFailure = true)。
  • 重试次数:最多重试 1 次(即总共执行2次请求,包括原始请求和一次重试)。这是一个硬编码上限,不可通过配置修改。
  • 触发场景:仅当连接建立阶段(TCP握手、TLS握手、HTTP连接复用)发生失败时触发。
  • 不触发场景:请求已发送并收到部分响应体、IO异常发生在读取应用层数据阶段、路由选择失败后无可选备用路由。

OkHttp的重试发生在 连接池 和 路由选择器 层面。当某个连接的建立失败时,OkHttp会检查是否存在其他备选路由(例如,域名解析出的多个IP、代理配置等)。若存在备选路由,OkHttp会使用新路由重新发起连接,这个过程对上层应用(如Retrofit)完全透明。

4.2 retryOnConnectionFailure 配置

该配置项提供全局开关来控制默认重试行为的启用与否。配置方式位于构建OkHttpClient时:

OkHttpClient client = new OkHttpClient.Builder()
    .retryOnConnectionFailure(true) // 或 false
    .build();
配置值 行为描述 适用场景
true 启用默认重试:连接失败时,最多重试1次使用备选路由。 公网服务、不稳定WiFi、需要容忍TCP层瞬断的生产环境。
false 禁用默认重试:任何连接失败都直接抛出IOException,不尝试备选路由。 内网高可靠通信、对幂等性有严格要求的场景、自定义拦截器层已经实现了更精细的重试策略时。
重要警告:即使在 retryOnConnectionFailure(false) 情况下,OkHttp仍然会执行 一次路由选择回退(比如从IPv6切换到IPv4),但这不归类为重试,而是路由层面的一次尝试。将 retryOnConnectionFailure 设为 false 并不会阻止OkHttp在每个路由上尝试连接,只是禁止在 所有已选路由都失败 后重新选择新路由并再次重试连接。

4.3 重试触发条件与排除条件

重试触发条件(满足任一即触发):
  1. 连接失败(TCP连接建立阶段,如 ConnectException、SocketTimeoutException 连接超时)。
  2. TLS/SSL握手失败(如证书错误、协议版本不匹配导致的 SSLHandshakeException)。
  3. 连接被重置(如收到RST包导致的 IOException)。
  4. HTTP/2 流错误或GoAway帧。
不触发重试的场景(直接向上层抛出异常):
  • 请求体已经通过 RequestBody.writeTo() 开始发送,且发送了部分或全部数据。(非幂等请求自动跳过重试)
  • 应用层读写IO异常(如 SocketException: Socket closed 发生在读取响应体期间)。
  • 超时异常中属于 SocketTimeoutException: Read timed out 或 SocketTimeoutException: Write timed out(仅连接超时 connect timed out 会触发重试)。
  • 路由选择阶段,所有备选路由均已尝试过且均失败。(默认重试次数已耗尽)。

4.4 默认重试流程可视化

OkHttp 默认重试决策流程图 发起请求(连接阶段) 连接是否成功? (TCP/TLS/路由) 请求/响应正常完成 结束(成功) 连接失败(产生异常) retryOnConnectionFailure 且匹配触发条件? 重试(使用备选路由) 最多重试1次,不可配置次数 直接向上层抛出异常 ⚠️ 关键说明 • 重试触发条件:连接超时、TCP连接拒绝、TLS握手失败、连接重置。 • 不触发条件:已发送请求体、应用层读/写超时、非幂等请求(默认跳过)、路由耗尽。 • OkHttp仅负责连接阶段的重试;应用层重试(如Retrofit拦截器)属于独立机制。

4.5 如何验证默认重试行为

通过本地启动一个仅监听端口的简易服务(如Python一行命令:python -m http.server 8080)。然后在客户端配置retryOnConnectionFailure(true),并在OkHttp的EventListener中重写connectStart、connectEnd、connectionFailure回调。日志中应观察到两次连接尝试,且第二次尝试使用不同的IP(若域名解析出多个IP)或不同的代理。

OkHttpClient client = new OkHttpClient.Builder()
    .eventListener(new EventListener() {
        @Override
        public void connectStart(Call call, InetSocketAddress inetSocketAddress, Proxy proxy) {
            System.out.println("[重试日志] 开始连接: " + inetSocketAddress);
        }
        @Override
        public void connectFailed(Call call, InetSocketAddress inetSocketAddress, Proxy proxy, Protocol protocol, IOException ioe) {
            System.out.println("[重试日志] 连接失败: " + inetSocketAddress + " 原因:" + ioe);
        }
    })
    .retryOnConnectionFailure(true)
    .build();
调试建议:使用OkHttp官方测试工具中的模拟故障代理,或在开发环境关闭主网卡并观察日志中 connectFailed 与 connectStart 的先后数量关系,可直观确认默认重试是否生效。