1、OkHttp简介与核心组件归纳
说到 Android 网络请求,OkHttp 几乎是绕不开的标配。我最早接触它的时候,还在用 HttpClient 那套老东西。后来项目里换了 OkHttp,第一感觉就是:清爽,而且默认就支持 HTTP/2。说实话,现在让我回去用别的方案,我是真不习惯了。
OkHttp 是什么?
OkHttp 是一款高效、稳定的 HTTP 客户端。它由 Square 公司开源,目前在 Android 和 Java 生态里占有率极高。它不只是简单的网络请求库,更像一个全功能的 HTTP 引擎。
它帮你处理了这些头疼的事:
- 连接复用(减少握手延迟)
- 自动重定向
- 请求/响应拦截
- 连接失败重试
- Cookie 持久化
嗯,这是它的主要功能。但很多人把它当黑盒用,直到线上崩溃才发现,很多问题出在初始化阶段。
核心组件拆解
要理解初始化崩溃,你得先搞懂它内部是怎么组装的。我之前在一个日活千万级的 App 里排查问题,发现崩溃往往不是 OkHttp 本身有 bug,而是组件配置不当导致的。
下面这张图,是我个人习惯用来向团队新人解释 OkHttp 内部结构的框架图。
每个组件都承担了什么角色?
| 组件 | 核心职责 | 初始化阶段注意点 |
|---|---|---|
| OkHttpClient | 全局配置容器,线程安全 | 构建时若传入空值或非法参数,直接抛异常 |
| Dispatcher | 调度异步请求,管理线程池 | 自定义线程池未启动?可能卡死 |
| ConnectionPool | 复用 TCP 连接,减少延迟 | 默认 5 分钟空闲回收,但配置不当会内存泄漏 |
| Interceptor | 拦截并处理请求/响应 | 自定义拦截器里做了网络请求?循环调用引发崩溃 |
| EventListener | 监控请求各阶段耗时 | 回调中操作 UI?非主线程抛异常 |
深入理解:为什么这些组件会引发初始化崩溃?
你想想看,OkHttpClient 的构建看起来很简单,不就是 new Builder() 然后 build() 吗?但实际项目里,很多人在这里翻车。比如:
常见错误示例:
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS) // 超时设置为0,等于无限等待?
.build();
嗯,超时传 0 在某些版本里等于不超时,但实际上底层会触发除零异常。我就在同事的代码里见到过这种写法。crash 日志直接指向 OkHttp 初始化。
Dispatcher 如何影响启动?
Dispatcher 内部维护了一个线程池。默认情况下,最大并发请求数是 64,每个主机的最大并发是 5。这个配置在绝大多数场景下够用。
但如果你自己传了一个自定义线程池进去,就得小心了。我之前遇到过这个问题:
- 自定义线程池的核心线程数设为 0
- 队列用 SynchronousQueue
- 结果第一个请求就把线程池塞满
- 后续请求全部被拒绝
- 崩溃直接抛 RejectedExecutionException
我曾经踩过的坑:
在 Application.onCreate() 里初始化 OkHttpClient,同时又在同一个线程池里执行第一个网络请求。如果 Dispatcher 的线程池没来得及启动,就会死锁。说白了,主线程等请求结果,请求等线程池启动,线程池又在等主线程释放锁——典型的闭环死锁。
ConnectionPool 的黑暗面
ConnectionPool 默认最多保持 5 个空闲连接,超过 5 分钟后回收。听起来挺合理是吧?但如果你在 Application 启动时一口气创建了 20 个 OkHttpClient 实例,每个都有自己的连接池——那后果就严重了。
我见过一个 App 启动时创建了 30 个 OkHttpClient 对象。每个连接池都占用线程和内存。最后 OOM 了。嗯,这里要注意:OkHttpClient 设计成全局单例是有道理的。
Interceptor 的顺序问题
拦截器分为应用拦截器和网络拦截器。它们在链中的顺序不同,执行的时机也不同。如果你在初始化时注册了多个拦截器,并且它们的执行顺序有依赖关系,那启动阶段的第一个请求就可能走错链路。
举个例子:
- 你加了一个 LoggingInterceptor 打印请求日志
- 又加了一个 HeaderInterceptor 添加鉴权头
- 如果 HeaderInterceptor 放在 LoggingInterceptor 后面,日志里就没有鉴权头
- 但这不会崩溃,只是逻辑错乱
- 真正崩溃的,是拦截器里又调用了 OkHttpClient 发起新请求,形成递归
我的建议:在初始化阶段,先只加必要的拦截器。像日志、统计这类非核心拦截器,等 App 启动完成再动态添加。这样既能避免初始化崩溃,又能保证核心功能不受影响。
核心初始化流程总结
再回头看整个初始化流程,其实就是一句话:
构建 OkHttpClient → 配置核心参数 → 注册拦截器 → 设置事件监听 → 准备就绪
每个环节都可能成为崩溃的导火索。特别是在多人协作的项目里,一个人改了线程池配置,另一个人改了超时时间,结果合到一起就爆炸了。
我习惯的做法是:写一个单独的 OkHttpClientProvider,把初始化逻辑封装起来,加上参数校验和异常捕获。这样就算配错了,也不至于让整个 App 闪退。
好了,OkHttp 的组件和它们可能引发的初始化问题,先聊到这儿。后续我们会深入每个组件的具体崩溃场景和解决方案。