4、OkHttp高级:连接池与复用、缓存策略、超时与重试、自定义DNS
好,咱们继续深入。前面几章我们把OkHttp的基础用法和拦截器机制聊透了。今天要讲的这几个点,说白了就是让你的网络请求「又快又稳又省钱」。连接池、缓存、超时重试、自定义DNS——每一个都是我实际项目中踩过坑的地方。
4.1 连接池与复用:别让TCP握手续命
你想想看,每次发一个HTTP请求,都要经历三次握手、四次挥手。如果请求频繁,这开销就大了去了。OkHttp怎么解决的?它搞了个连接池,把用过的TCP连接缓存起来,下次请求直接复用。
我个人习惯,在构建OkHttpClient时,会显式配置连接池参数:
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
.build();
这里两个参数:最大空闲连接数5个,空闲存活时间5分钟。什么意思?就是池子里最多保留5个空闲连接,超过5分钟没人用就干掉。
OkHttp默认的连接池是5个连接,5分钟超时。大部分场景够用。但如果你做的是长连接应用,比如IM或推送,建议调大连接数,缩短超时时间。
4.2 缓存策略:让数据少跑网线
缓存这东西,说白了就是用空间换时间。OkHttp的缓存机制基于HTTP协议本身的缓存头,比如Cache-Control、Expires、ETag这些。
先配缓存目录和大小:
File cacheDir = new File(context.getCacheDir(), "http_cache");
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 10MB
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
然后服务端返回的响应头里带上缓存策略。比如:
Cache-Control: max-age=3600
这表示这个响应在1小时内可以直接用缓存,不用发请求。
但问题来了——服务端不一定配合。我遇到过很多后端同学根本不设缓存头。怎么办?
嗯,这里要注意。你可以在拦截器里手动添加缓存策略:
Interceptor cacheInterceptor = chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
// 如果服务端没设缓存,我们强制缓存5分钟
if (response.cacheControl().maxAgeSeconds() == -1) {
return response.newBuilder()
.header("Cache-Control", "max-age=300")
.build();
}
return response;
};
4.3 超时与重试:别让用户等太久
网络请求最怕什么?卡死。用户等个十几秒没反应,直接关应用了。所以超时设置是必须的。
OkHttp提供了三个超时维度:
| 超时类型 | 默认值 | 说明 |
|---|---|---|
| connectTimeout | 10秒 | 建立连接的超时时间 |
| readTimeout | 10秒 | 读取数据的超时时间 |
| writeTimeout | 10秒 | 写入数据的超时时间 |
我个人习惯这样配:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
为什么读和写设30秒?因为上传大文件或下载大文件时,10秒可能不够。连接超时反而要短一点,15秒连不上就别等了。
再说重试。OkHttp默认不会自动重试,但你可以通过拦截器实现:
Interceptor retryInterceptor = chain -> {
Request request = chain.request();
Response response = null;
int retryCount = 0;
while (retryCount < 3) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
// 网络异常才重试
retryCount++;
if (retryCount >= 3) {
throw e;
}
// 等一会儿再重试
Thread.sleep(1000 * retryCount);
}
}
return response;
};
4.4 自定义DNS:打破域名解析的限制
DNS解析,说白了就是把域名转成IP。默认情况下,OkHttp用的是系统DNS。但有些场景需要自定义:
- 你想绕过DNS污染
- 你想做域名到IP的映射(比如测试环境)
- 你想实现动态域名解析
OkHttp提供了Dns接口,实现起来很简单:
OkHttpClient client = new OkHttpClient.Builder()
.dns(new Dns() {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if ("api.example.com".equals(hostname)) {
// 直接返回固定IP
return Collections.singletonList(
InetAddress.getByAddress(
new byte[]{(byte)192, (byte)168, 1, 100}
)
);
}
// 其他域名走系统DNS
return Dns.SYSTEM.lookup(hostname);
}
})
.build();
更高级的用法,你可以集成HTTPDNS服务。比如阿里云HTTPDNS,它能避免Local DNS劫持,提升解析速度。
还有个细节:自定义DNS返回的IP列表,OkHttp会按顺序尝试连接。如果第一个IP连不上,会自动尝试下一个。这相当于内置了故障转移。
小结
今天这四个点,说白了就是让网络请求更高效、更稳定。连接池省握手开销,缓存省网络流量,超时重试防卡死,自定义DNS解域名问题。
嗯,这些配置看起来简单,但每个参数背后都有实际场景的考量。我建议你在项目里动手试试,调一调参数,看看效果。纸上得来终觉浅,绝知此事要躬行。
下一章我们聊聊Retrofit,它是OkHttp的最佳拍档。到时候你会发现,很多今天讲的东西,在Retrofit里都能用上。