OkHttp深度集成:从配置到拦截器,打造可靠网络层
说实话,Retrofit只是网络层的"门面",真正干活的其实是OkHttp。我见过不少项目,Retrofit用得挺溜,但OkHttp的配置就随便写写,结果线上各种超时、日志混乱、重试策略一团糟。今天咱们就把这块彻底讲透。
核心观点:OkHttp是Android网络层的基石,它的配置质量直接决定了App的网络稳定性。别把它当黑盒用,得理解它的工作原理。
OkHttpClient配置:你的网络管家
每个OkHttpClient实例都管理着自己的连接池、线程池和配置。我个人习惯在Application初始化时只创建一个单例,避免重复创建浪费资源。
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
.readTimeout(30, TimeUnit.SECONDS) // 读取超时
.writeTimeout(30, TimeUnit.SECONDS) // 写入超时
.connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES)) // 连接池
.retryOnConnectionFailure(true) // 连接失败重试
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE
})
.build()
这里有个坑,我曾经遇到过:超时时间设得太短。有一次用户反馈在弱网环境下图片加载失败,查了半天发现是connectTimeout只设了5秒。在2G/3G网络下,5秒根本不够完成TCP握手。后来我改成30秒,问题就解决了。
我的建议:超时时间要根据业务场景来定。文件上传可以设长一点(60秒+),普通API请求30秒足够。别一刀切。
拦截器原理:请求的"中间人"
拦截器是OkHttp最强大的扩展点。说白了,它就像快递中转站——每个包裹(请求)经过时,你可以检查、修改、甚至拦截它。
OkHttp有两种拦截器:
| 类型 | 添加方式 | 执行顺序 | 典型用途 |
|---|---|---|---|
| 应用拦截器 | addInterceptor() | 最先执行,最后响应 | 添加公共Header、打印日志 |
| 网络拦截器 | addNetworkInterceptor() | 在重定向/重试之后 | 监控网络流量、缓存控制 |
你想想看,应用拦截器是在请求发出前就介入,而网络拦截器是在真正发起网络请求时才触发。这个区别很重要——如果你用应用拦截器打印日志,重定向的中间请求是看不到的;但网络拦截器能看到每一次真实的网络往返。
// 自定义拦截器示例
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
// 添加Token
val newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer ${getToken()}")
.build()
// 记录请求耗时
val startTime = System.currentTimeMillis()
val response = chain.proceed(newRequest)
val duration = System.currentTimeMillis() - startTime
Log.d("OkHttp", "${newRequest.url} 耗时 ${duration}ms")
return response
}
}
注意:在拦截器里不要做耗时操作,比如数据库查询。因为拦截器运行在OkHttp的线程池中,阻塞它会拖慢整个请求队列。
日志拦截器:调试利器
HttpLoggingInterceptor是Square官方提供的日志工具。我刚开始用的时候,直接设成BODY级别就上线了——结果用户日志里全是请求体和响应体,又大又敏感。后来学乖了,用BuildConfig控制级别。
val loggingInterceptor = HttpLoggingInterceptor { message ->
// 可以自定义日志输出,比如写入文件
Log.d("OkHttp", message)
}.apply {
level = when {
BuildConfig.DEBUG -> HttpLoggingInterceptor.Level.BODY
else -> HttpLoggingInterceptor.Level.NONE
}
}
日志级别有四种:
- NONE:不打印任何日志
- BASIC:只打印请求方法和响应状态码
- HEADERS:打印请求和响应的头部信息
- BODY:打印完整的请求体和响应体
我个人习惯在测试环境用BODY,预发布环境用HEADERS,生产环境用NONE。这样既能调试,又不会泄露数据。
超时与重试机制:别让用户等太久
超时和重试是网络层的"安全网"。但怎么设、设多少,这里面有门道。
OkHttp提供了三个超时维度:
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时:TCP握手时间
.readTimeout(30, TimeUnit.SECONDS) // 读取超时:两个数据包之间的间隔
.writeTimeout(30, TimeUnit.SECONDS) // 写入超时:发送数据的间隔
.build()
我曾经犯过一个错误:把readTimeout设成和connectTimeout一样短。结果用户上传图片时,因为网络波动导致写入间隔稍长,就直接超时了。记住,readTimeout和writeTimeout是针对数据流的,不是整个请求的耗时。
重试机制我建议用自定义拦截器实现,而不是依赖OkHttp自带的retryOnConnectionFailure。因为自带的只重试一次,而且不区分错误类型。
class RetryInterceptor(private val maxRetryCount: Int = 3) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var retryCount = 0
var response: Response? = null
while (retryCount < maxRetryCount) {
try {
response = chain.proceed(chain.request())
// 如果响应成功,直接返回
if (response.isSuccessful) return response
// 5xx错误才重试
if (response.code in 500..599) {
retryCount++
if (retryCount < maxRetryCount) {
delay(1000L * retryCount) // 指数退避
response.close()
continue
}
}
return response
} catch (e: IOException) {
// 网络异常才重试
retryCount++
if (retryCount >= maxRetryCount) throw e
delay(1000L * retryCount)
}
}
return response ?: throw IOException("重试失败")
}
}
这个拦截器有几个要点:
- 只重试5xx服务器错误和IOException网络异常
- 4xx客户端错误不重试——那是你代码的问题,重试也没用
- 用指数退避策略,第一次等1秒,第二次等2秒,避免雪崩
- 记得关闭失败的Response,防止连接泄漏
避坑指南:重试时一定要考虑幂等性。POST请求重试可能导致重复下单,建议只对GET请求做自动重试。非幂等请求可以提示用户手动重试。
知识体系总览
下面这张图把OkHttp集成的核心脉络梳理清楚了,你可以对照着检查自己的项目:
嗯,到这里OkHttp的核心配置就讲完了。其实说白了,网络层的稳定性就靠这几个配置项撑着。你想想看,超时设不好用户骂娘,日志没配好线上查问题抓瞎,重试没做用户点一下按钮等半天——这些坑我都踩过,所以希望你能一次搞定。
公众号:蓝海资料掘金营,微信deep3321