OkHttp 与 Retrofit:到底谁依赖谁?
很多人刚开始学 Retrofit 的时候,会以为它是个独立的网络库。其实——说白了,Retrofit 就是个「壳」,真正干活的是 OkHttp。我习惯把 Retrofit 比作一个智能调度员,而 OkHttp 是那个开着货车满街跑的司机。今天我们就来拆开看看,它们之间到底是怎么配合的。
OkHttp 拦截器链:请求的必经之路
OkHttp 最核心的设计就是 拦截器链(Interceptor Chain)。每个请求从发出到收到响应,会依次经过一系列拦截器。你可以把它想象成流水线上的工位,每个工位负责一件事。
// 一个典型拦截器链的顺序
1. 应用层拦截器(addInterceptor 添加)
2. RetryAndFollowUpInterceptor (重试与重定向)
3. BridgeInterceptor (桥接,处理请求头/响应头)
4. CacheInterceptor (缓存)
5. ConnectInterceptor (建立连接)
6. ... 网络层拦截器 ...
7. 应用层网络拦截器(addNetworkInterceptor 添加)
我自己当年刚接触时,总搞不清 addInterceptor 和 addNetworkInterceptor 有什么区别。其实很简单:前者只走一次,不管是否走缓存;后者只要真正发起网络请求就会走,包括重试。嗯,这里要注意:千万别用网络拦截器去打印日志,否则重试时会重复打印好几遍。我曾经就因为这个被老板骂过……
避坑指南:拦截器调用顺序是固定的,但你可以通过 addInterceptor(0, interceptor) 来插队到最前面。不过我不建议滥用,除非你真的清楚每次拦截器执行的时机。
连接池:TCP 连接的复用
HTTP/1.1 默认支持 keep-alive,但如果没有连接池,每次请求还是要经过三次握手。OkHttp 内置了 ConnectionPool,默认最多保持 5 个空闲连接,每个连接闲置 5 分钟后关闭。
| 参数 | 默认值 | 说明 |
|---|---|---|
| maxIdleConnections | 5 | 最多保留的空闲连接数 |
| keepAliveDuration | 5 分钟 | 空闲连接存活时间 |
你可能会问:「连接池大一点不是更好吗?」其实不是。我做过压测,连接池太大反而会占用过多文件描述符,移动端尤其明显。建议根据并发量调小一点,比如 2~3 个。
缓存机制:让网络少跑一趟
OkHttp 的缓存基于 HTTP 缓存协议(Cache-Control、ETag、Last-Modified 等)。它不像前端缓存那样自动缓存所有请求,你得提前告诉它「哪些路径可以缓存」。常用的做法是配合服务端返回的缓存头来做——如果服务器没返回缓存控制头,OkHttp 默认不缓存。
// 配置 10MB 磁盘缓存
File cacheDir = new File(context.getCacheDir(), "http");
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
这里有个容易翻车的点:POST 请求默认不缓存。虽然可以通过自定义拦截器强行缓存,但我不建议这么干——和 REST 规范冲突,容易出诡异的 bug。
警告:不要指望缓存能解决所有网络问题。我曾经试图用缓存来做离线模式,结果发现服务端返回的 Cache-Control 是 no-cache……最后还是老老实实走了本地数据库缓存。
Retrofit 如何基于 OkHttp 工作?
Retrofit 的核心流程是:接口定义 → 动态代理 → CallAdapter 适配 → OkHttp 执行。说白了,你写一个 interface ApiService,Retrofit 在运行时会生成一个代理对象。当你调用 getUser() 时,代理对象会把它翻译成一个 Request,交给 OkHttp 去发。
关键点在于:Retrofit 自己根本不解析 URL、不组装请求体,它只是把接口注解(@GET、@POST 等)转换成 OkHttp 的 Request.Builder。然后通过 OkHttp 的 Call 对象执行请求。你可以理解为:Retrofit 是「剧本」,OkHttp 是「演员」。
另外,Retrofit 的 Converter(Gson、Moshi 等)和 CallAdapter(RxJava、Kotlin Coroutines 等)也是挂载在 OkHttp 之上的。以 GsonConverterFactory 为例,它会在 onResponse 回调里拿到 ResponseBody 的输入流,然后用 Gson 解析成你的数据类。
小技巧:如果你想自定义异常处理(比如统一处理 401),不要在 Retrofit 的 Callback 里写死,而是加一个 OkHttp 的拦截器。这样一套逻辑所有接口共享,代码干净很多。
一张图看懂全局
总结几个我个人常犯的错误
- 拦截器顺序依赖:如果你在应用拦截器里修改了请求头,而连网拦截器又依赖那个头,那就要小心了——网络拦截器拿到的已经是修改后的版本。
- 连接池在多域名场景下失效:连接池是按
host:port分的,如果你的服务用了多个 CDN 域名,连接池效果会大打折扣。我曾在某次上线后才发现这个问题,赶紧让服务端把所有 API 收敛到一个域名下。 - 缓存与动态 URL 冲突:如果你用
@Url动态拼接参数,服务端返回的 Cache-Control 是 no-cache,那么缓存策略跟你预期完全不同——别问我怎么知道的。
理解了 OkHttp 这层底子,后面讲 Mock 拦截器你就知道该往哪插了。搞定了拦截器链,基本就掌握了 OkHttp 的「魂」。