OkHttp
-
- 1、核心特性与架构设计
- [2、核心 API 分类列表全景图](#2、核心 API 分类列表全景图)
- [3、常用 API 与实战代码示例](#3、常用 API 与实战代码示例)
- 4、避坑指南与最佳实践
OkHttp 是由 Square 公司开源的高性能 HTTP & HTTP/2 客户端,也是目前 Java / Android 生态中最主流、事实上的 HTTP 网络库标准(Android 官方从 Android 4.4 起底层已将 HttpURLConnection 替换为 OkHttp 实现)。
1、核心特性与架构设计
- HTTP/2 与连接池支持:共享同一个 Socket 连接,大幅降低延迟与 TCP 握手开销。
- 透明的 GZIP 压缩:自动压缩请求体并解压响应体,节省网络流量。
- 响应缓存:支持 HTTP 响应头的本地缓存,避免重复的网络请求。
- 拦截器链(Interceptor Chain):极其强大的责任链设计模式,允许轻松插拔日志记录、统一 Token 注入、失败重试、自动重定向等功能。
- 基于 Okio 的高性能 IO:底层结合 Square 的 Okio 库,在缓冲管理和内存占用上性能顶尖。
2、核心 API 分类列表全景图
| 核心类 (Class) | 角色与功能说明 |
|---|---|
| OkHttpClient | 全局单例配置中心。管理连接池、超时时间、拦截器、缓存等。通常全局共享一个实例。 |
| Request | HTTP 请求描述。使用 Builder 模式构建,包含 URL、HTTP 方法、请求头(Headers)、请求体(RequestBody)。 |
| Response | HTTP 响应结果。包含状态码(Code)、响应头、响应体(ResponseBody)。注意:响应体流只能读取一次且必须关闭。 |
| Call | 请求执行任务。代表一个已准备就绪的 HTTP 请求,可同步(execute())或异步(enqueue())执行,支持取消操作。 |
| RequestBody | 请求体抽象。支持传输 JSON、Form 表单、Multipart 多部分文件上传、流式数据等。 |
| ResponseBody | 响应体抽象。支持将响应转为 String、byte\[\] 或 InputStream(用于大文件下载)。 |
| Interceptor | 拦截器接口。应用拦截器(Application Interceptor)与网络拦截器(Network Interceptor)的核心入口。 |
3、常用 API 与实战代码示例
依赖引入 (Maven / Gradle)
xml
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version> <!-- 推荐使用 4.x/5.x 稳定版 -->
</dependency>
1. 初始化全局 OkHttpClient
最佳实践:OkHttpClient 内部拥有连接池和线程池,千万不要在每次请求时 new OkHttpClient(),必须作为单例复用,或者通过 .newBuilder() 基于现有配置衍生新对象。
java
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class OkHttpConfig {
// 全局单例
public static final OkHttpClient CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
.writeTimeout(10, TimeUnit.SECONDS) // 写入超时
.retryOnConnectionFailure(true) // 失败重试
.build();
}
2. 同步与异步 GET 请求
① 同步 GET 请求 (execute)
java
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public void syncGetDemo() {
Request request = new Request.Builder()
.url("https://api.github.com/users/octocat")
.header("User-Agent", "OkHttp-Demo") // 添加 Header
.build();
// try-with-resources 自动关闭 Response / ResponseBody 释放底层 Socket
try (Response response = OkHttpConfig.CLIENT.newCall(request).execute()) {
if (!response.isSuccessful()) {
System.err.println("请求失败, 状态码: " + response.code());
return;
}
ResponseBody body = response.body();
if (body != null) {
String jsonResult = body.string(); // 获取字符串格式的响应体
System.out.println("响应内容: " + jsonResult);
}
} catch (Exception e) {
e.printStackTrace();
}
}
② 异步 GET 请求 (enqueue)
java
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public void asyncGetDemo() {
Request request = new Request.Builder()
.url("https://api.github.com/users/octocat")
.build();
// 放入异步线程池队列中执行,不阻塞当前线程
OkHttpConfig.CLIENT.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.err.println("网络请求异常: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
if (response.isSuccessful() && body != null) {
System.out.println("异步响应结果: " + body.string());
}
}
}
});
}
3. POST 请求 (JSON & 表单提交)
① 发送 JSON 数据 (MediaType)
java
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public void postJsonDemo() throws Exception {
MediaType JSON = MediaType.get("application/json; charset=utf-8");
String jsonString = "{\"name\":\"Alice\", \"age\":25}";
// 构建 RequestBody
RequestBody body = RequestBody.create(jsonString, JSON);
Request request = new Request.Builder()
.url("https://httpbin.org/post")
.post(body)
.build();
try (Response response = OkHttpConfig.CLIENT.newCall(request).execute()) {
System.out.println("POST 返回: " + response.body().string());
}
}
② 提交 Form 表单数据 (FormBody)
java
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public void postFormDemo() throws Exception {
// 模拟常规 HTML 表单提交
RequestBody formBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/post")
.post(formBody)
.build();
try (Response response = OkHttpConfig.CLIENT.newCall(request).execute()) {
System.out.println("Form 返回: " + response.body().string());
}
}
4. 文件上传 (MultipartBody) 与 文件下载
① Multipart 多部分文件上传
java
import okhttp3.*;
import java.io.File;
public void uploadFileDemo(File uploadFile) throws Exception {
RequestBody fileBody = RequestBody.create(uploadFile, MediaType.parse("image/png"));
// 构建包含表单参数和文件对象的 MultipartBody
RequestBody multipartBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("user", "JohnDoe")
.addFormDataPart("avatar", uploadFile.getName(), fileBody)
.build();
Request request = new Request.Builder()
.url("https://httpbin.org/post")
.post(multipartBody)
.build();
try (Response response = OkHttpConfig.CLIENT.newCall(request).execute()) {
System.out.println("文件上传响应: " + response.code());
}
}
② 文件流下载
java
import okhttp3.*;
import java.io.FileOutputStream;
public void downloadFileDemo(String fileUrl, File saveFile) throws Exception {
Request request = new Request.Builder().url(fileUrl).build();
try (Response response = OkHttpConfig.CLIENT.newCall(request).execute()) {
if (!response.isSuccessful()) return;
// 使用 InputStream 分块写入本地文件,防止大文件导致 OOM
try (var is = response.body().byteStream();
var fos = new FileOutputStream(saveFile)) {
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
}
}
}
5. 核心高级功能:拦截器(Interceptor)
拦截器是 OkHttp 最强大的拓展机制。常用于统一添加认证 Token、打印日志、修改请求头等。
java
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
// 1. 定义一个用于自动注入 Auth Token 的拦截器
public class AuthInterceptor implements Interceptor {
private final String token;
public AuthInterceptor(String token) {
this.token = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// 拦截并修改 Request:追加 Authorization 请求头
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token)
.build();
// 放行请求,交给责任链中的下一个节点执行
return chain.proceed(authenticatedRequest);
}
}
// 2. 挂载到 OkHttpClient 示例
OkHttpClient clientWithAuth = OkHttpConfig.CLIENT.newBuilder()
.addInterceptor(new AuthInterceptor("secret_token_123"))
.build();
4、避坑指南与最佳实践
- 务必显式关闭 Response 或 ResponseBody:
body.string() 或 body.byteStream() 底层持有 Socket 连接流,如果未关闭会导致连接无法复用并发生连接池/句柄泄漏。建议统一使用 try-with-resources 语法。 - body.string() 只能被调用一次:
调用 body.string() 后,响应流的内容会被全部读取并从内存缓存中释放。第二次调用会直接抛出 IllegalStateException: closed。如果需要二次读取,需在拦截器层缓存 Buffer。 - 避免在大文件下载时使用 body.string() 或 body.bytes():
这会将整个文件一次性全量载入 JVM 堆内存,极易引发 OutOfMemoryError(OOM)。大文件应使用 body.byteStream() 流式边读边写。