OkHttp作为Android和Java生态中应用广泛的HTTP客户端,凭借连接池、请求拦截器、缓存机制以及HTTP/2支持等特性,被大量项目用于网络通信。然而,在真实业务环境中,网络请求并不总是一帆风顺,弱网络、服务器短暂不可用、连接超时、移动网络切换等情况都可能导致HTTP请求失败。
为了提升应用稳定性,很多开发者会为OkHttp设计请求重连机制。合理的HTTP请求重连策略能够减少偶发网络异常带来的失败,提高接口调用成功率,同时改善用户体验。本文将详细介绍基于OkHttp实现HTTP请求重连机制的方法、核心原理以及实际开发中的注意事项。
一、为什么需要HTTP请求重连机制
网络环境具有天然的不确定性,即使服务器和客户端代码完全正常,请求依然可能因为以下原因失败:
手机从WiFi切换到移动网络导致连接断开;
DNS解析失败;
TCP连接建立超时;
服务端短暂重启;
网络丢包导致请求超时;
代理服务器异常;
服务端返回临时性错误。
例如,一个支付查询接口在请求过程中因为网络波动失败,如果客户端直接提示用户失败,会影响业务体验。通过请求重连机制,可以等待短暂时间后自动再次尝试,从而提高请求成功概率。
不过,需要注意的是,重连并不是所有失败请求都应该执行。对于参数错误、权限不足、业务校验失败等明确无法成功的请求,重复发送只会增加服务器压力。
二、OkHttp自带的连接恢复能力
很多开发者误认为OkHttp没有重连功能,实际上OkHttp内部已经提供了一定程度的连接恢复能力。
OkHttp默认支持:
自动恢复部分连接失败;
连接池复用;
路由切换;
HTTP代理失败切换;
TLS握手异常恢复。
例如:
OkHttpClient client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.build();retryOnConnectionFailure(true)表示开启连接失败自动恢复功能。
但是,这种机制主要针对底层连接问题,并不等同于完整的业务请求重试。
例如:
HTTP 500服务器错误不会自动重新请求;
HTTP 429限流不会自动等待;
请求超时后的业务重试需要自行实现;
POST请求是否重试需要开发者判断。
因此,在实际项目中通常需要基于OkHttp扩展更完善的重连策略。
三、OkHttp实现请求重连的核心思路
一个完整的HTTP请求重连机制通常包含以下几个部分:
1. 设置最大重试次数
无限重试会造成严重问题,因此必须限制次数。
例如:
private static final int MAX_RETRY_COUNT = 3;表示最多请求3次。
一般建议:
普通查询接口:2~3次;
核心业务接口:根据业务容忍度调整;
上传接口:谨慎重试。
2. 判断是否允许重试
不是所有异常都适合重新请求。
常见可重试情况:
IOException;
SocketTimeoutException;
ConnectException;
HTTP 502;
HTTP 503;
HTTP 504。
不建议重试:
HTTP 400;
HTTP 401;
HTTP 403;
参数校验失败。
判断逻辑示例:
private boolean shouldRetry(Response response) {
int code = response.code();
return code == 502
|| code == 503
|| code == 504;
}四、通过Interceptor实现OkHttp请求重试
OkHttp提供Interceptor机制,可以在请求执行过程中统一处理重试逻辑。
示例:
public class RetryInterceptor implements Interceptor {
private int maxRetry;
public RetryInterceptor(int maxRetry) {
this.maxRetry = maxRetry;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
int retryCount = 0;
while (retryCount < maxRetry) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
if (!needRetry(response)) {
return response;
}
} catch (IOException e) {
if (retryCount >= maxRetry - 1) {
throw e;
}
}
retryCount++;
try {
Thread.sleep(1000L * retryCount);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return response;
}
private boolean needRetry(Response response) {
int code = response.code();
return code == 502
|| code == 503
|| code == 504;
}
}添加Interceptor:
OkHttpClient client =
new OkHttpClient.Builder()
.addInterceptor(new RetryInterceptor(3))
.build();这样所有通过该OkHttpClient发出的请求都会经过统一重试处理。
五、使用指数退避策略优化重连
简单固定时间等待虽然容易实现,但效果有限。
例如:
第一次失败等待1秒;
第二次失败等待2秒;
第三次失败等待4秒。
这种方式称为指数退避(Exponential Backoff)。
计算方式:
long delay = (long) Math.pow(2, retryCount) * 1000;优势包括:
避免大量客户端同时重试;
降低服务器瞬间压力;
给故障恢复留出时间。
在高并发系统中,例如订单系统、消息系统,指数退避几乎是标准设计方案。
六、处理POST请求重试问题
GET请求通常没有副作用,因此失败后重新发送比较安全。
但是POST请求需要特别注意。
例如:
用户提交订单:
第一次请求已经到达服务器;
服务器返回超时;
客户端认为失败重新发送。
可能导致:
创建两个订单;
重复扣款;
数据重复写入。
解决方式主要有两种:
1. 使用幂等设计
服务端提供唯一业务编号:
requestId=202608250001服务器收到重复请求时,根据requestId判断是否已经处理。
2. 控制请求类型
对于非幂等操作:
不自动重试;
交由业务层确认。
七、结合Call机制实现单个请求重试
除了Interceptor,也可以针对单个请求实现重试。
示例:
public void requestWithRetry(
OkHttpClient client,
Request request) throws IOException {
int retry = 0;
while (retry < 3) {
try {
Response response =
client.newCall(request).execute();
if(response.isSuccessful()){
return;
}
} catch(IOException e){
if(retry == 2){
throw e;
}
}
retry++;
}
}这种方式适合:
少量特殊接口;
上传任务;
下载任务;
后台同步任务。
如果项目中大量接口需要统一处理,更推荐Interceptor方案。
八、OkHttp异步请求中的重连处理
实际开发中更多使用异步请求:
client.newCall(request)
.enqueue(new Callback() {
@Override
public void onFailure(
Call call,
IOException e) {
}
@Override
public void onResponse(
Call call,
Response response) {
}
});异步模式下不能直接使用while循环阻塞线程。
更推荐:
在失败回调中重新创建Call;
配合Handler或协程延迟执行;
使用任务调度框架控制重试。
例如:
private void retryRequest(Call call){
call.clone().enqueue(callback);
}clone()可以复制一个新的Call对象,避免重复执行已经使用过的Call。
九、设置合理的超时时间
重连机制必须配合超时策略,否则一次失败可能等待很久。
OkHttp配置:
OkHttpClient client =
new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)