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深度集成知识体系 OkHttpClient配置 • 连接超时:connectTimeout • 读写超时:readTimeout / writeTimeout • 连接池:connectionPool 复用管理 拦截器原理 • 应用拦截器:最先执行,最后响应 • 网络拦截器:重定向/重试后触发 • 自定义拦截器:认证、日志、重试 日志拦截器 • NONE:不打印日志 • BASIC:方法 + 状态码 • HEADERS:头部信息 • BODY:完整请求/响应体 超时与重试机制 • 三超时:连接/读取/写入 • 重试策略:指数退避 • 幂等性检查:GET可重试 • 错误区分:5xx / IOException 配置 → 拦截 → 日志 → 容错,四步打造可靠网络层

嗯,到这里OkHttp的核心配置就讲完了。其实说白了,网络层的稳定性就靠这几个配置项撑着。你想想看,超时设不好用户骂娘,日志没配好线上查问题抓瞎,重试没做用户点一下按钮等半天——这些坑我都踩过,所以希望你能一次搞定。


公众号:蓝海资料掘金营,微信deep3321