OkHttp 框架简介与核心概念

老实说,我第一次接触 OkHttp 是在 2015 年做的一个即时通讯项目。那时候 Android 网络库还群雄割据,HttpURLConnection 难用,Volley 又不够灵活。直到我试了 OkHttp——嗯,感觉就像从手动挡换成了自动挡,而且还配了涡轮增压。

OkHttp 是 Square 公司开源的 HTTP 客户端,2013 年发布第一个版本。不到十年,它就成了 Android 开发者的标配。Google 甚至在 Android 4.4 之后把内部的 HttpURLConnection 实现换成了 OkHttp。这件事让我印象很深——连官方都认可的东西,你想想看,质量差不了。

核心思想:OkHttp 把网络请求的每个环节都抽象成可插拔的组件,让你既能开箱即用,又能按需定制。

从历史看设计哲学

我记得 OkHttp 1.0 的时候还只是个简单的同步 HTTP 客户端。到了 2.0 引入了拦截器(Interceptor),这个设计太聪明了——它把请求/响应的处理链条化,你可以插入日志、重试、缓存、甚至 mock 数据。我后来在项目中做网络监控时,就靠拦截器悄无声息地采集了所有请求耗时。

当前稳定版是 OkHttp 4.x,它用 Kotlin 重写了内部实现,但对外 API 完全兼容 Java。说白了,你以前写的 OkHttp 3.x 代码,一个字都不用改就能跑到 4.x 上。这种向下兼容的态度,值得尊重。

三大核心类:OkHttpClient、Request、Call

一开始接触 OkHttp,你只需要搞懂三个类。我把它们比作一场外卖订单:OkHttpClient 是厨房(配置),Request 是菜单(描述),Call 是骑手(执行)。

核心类职责典型配置
OkHttpClient 管理连接池、超时、拦截器、缓存等全局配置 连接超时 10s, 拦截器列表, CookieJar
Request 描述一次 HTTP 请求:URL、方法、Headers、Body GET/POST, 添加 Header, 设置请求体
Call 代表一次即将或正在执行的请求,可同步/异步发起 enqueue() 异步, execute() 同步
避坑指南:我曾经在项目里每个 Activity 都 new 一个 OkHttpClient,结果连接池互相隔离,TCP 连接反复创建,内存泄漏还带上了 ANR。记住:全局单例 OkHttpClient,别乱 new。

Builder 模式:让配置不再头疼

我猜你一定见过这种代码:

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .addInterceptor(new LoggingInterceptor())
    .cookieJar(new PersistentCookieJar())
    .build();

Request request = new Request.Builder()
    .url("https://api.example.com/data")
    .header("Authorization", "Bearer xxx")
    .get()
    .build();

Call call = client.newCall(request);

这个 Builder 模式,说白了就是让你用“链式调用”一口气完成所有配置。为什么要这么设计?因为 OkHttpClient 和 Request 都有太多可选参数,如果都用构造方法重载,光看参数列表就得疯。Builder 模式把一个构造函数的“大爆炸”拆成了十几个方法调用,每个方法只做一件事,代码可读性瞬间提升了。

我个人习惯是:把 OkHttpClient 的建造抽取成方法,比如 createDefaultClient(),然后只在需要特殊配置的地方才加 Builder。这样团队里不会出现“一人一个超时时间”的混乱。

架构一张图看懂

下面这张图我特意画成了“厨房 → 菜单 → 骑手 → 送餐”的类比,方便你记忆。箭头表示调用/数据流向。

OkHttp 核心架构:厨房·菜单·骑手 OkHttpClient 连接池 / 超时 / 拦截器 🔧 全局配置 · 单例复用 Request URL / Method / Headers / Body 📋 描述一次请求 Call enqueue() / execute() 🚀 执行请求 · 异步/同步 newBuilder() newCall() Response body() · code() · headers() 请求执行后返回 Builder 模式:链式建造 • OkHttpClient.Builder() → .connectTimeout() → .addInterceptor() → .build() • Request.Builder() → .url() → .header() → .method() → .build()

实战中容易踩的坑

我记得有一次帮同事定位线上问题——某个接口总是超时,抓包发现请求根本没发出去。查了半天,原来是他在 Builder 里给 OkHttpClient 配置了一个自定义 Dns,那个 Dns 解析回调里抛了异常,导致整个请求链断掉。你看,Builder 虽然方便,但每个环节都可能藏着陷阱。

高频坑点:
  • OkHttpClient 重用:每次请求都 new 一个 client,别这样。连接池、线程池全浪费了。
  • Call 只能执行一次:Call 对象设计为单次执行。想重试?要么 newCall,要么用 clone()。
  • Builder 创建的 Request 是不可变对象:如果你拿到 Request 实例后想修改 URL,只能重新建一个 Builder。

一个小对比:OkHttp vs 其他网络库

维度OkHttpVolleyRetrofit
定位HTTP 客户端(底层)网络请求框架(含缓存、请求队列)REST 接口封装(上层)
连接复用✅ 内置连接池❌ 每次请求新建连接✅ 基于 OkHttp
自定义极高(拦截器任意插拔)中高(配合 OkHttp)
学习成本低(三个类)中中(注解语法)

我自己团队的项目现在都用 Retrofit + OkHttp 的组合。Retrofit 负责把接口定义成 Java 接口,OkHttp 负责真正的网络通信。但如果只是简单发个 GET/POST,直接用 OkHttp 就很舒服,没必要多套一层。

经验之谈:做性能优化时,我先看 OkHttp 的连接池配置——默认最大空闲连接 5 个,存活时间 5 分钟。如果你的应用短时间内发起大量并发请求,可以把 maxIdleConnections 调大一些,比如 10 个。我曾经在直播项目中调成 20,TCP 三次握手从每次请求 35ms 降到零,效果立竿见影。

好了,OkHttp 的三个核心概念就聊到这。下一个实战环节我们直接搭一个混合场景——用 OkHttp 发请求的同时,还要监听蓝牙 GATT 回调。这种场景下,线程模型和内存泄漏最容易出现,我们到时候细说。