一,介绍
OkHttp 是一款广泛使用的开源 HTTP 客户端库,由 Square 公司开发,专为 Android 和 Java 应用设计。它以高效、灵活和功能强大著称,支持同步和异步请求,并提供了丰富的功能来简化网络通信。
OkHttp 的核心优势
- 高性能
- 内置连接池,减少延迟和重复连接开销。
- 支持 HTTP/2 和 WebSocket,提升传输效率。
- 自动 GZIP 压缩,减少数据传输量。
- 简洁易用的 API
- 链式调用设计,代码可读性强。
- 支持同步和异步请求,灵活适配不同场景。
- 强大的扩展性
- 拦截器(Interceptor):可自定义请求/响应处理逻辑(如日志、重试、缓存)。
- 支持 Cookie 持久化、缓存控制、HTTPS 安全配置等。
- 广泛的应用场景
- 适用于 Android 应用、Java 后端服务等。
- 是 Retrofit 等流行库的底层实现。
二,快速入门
- 添加依赖
```xml
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
```
2. 发送get请求
```java
OkHttpClient client = new OkHttpClient();
// 构建 GET 请求
Request request = new Request.Builder()
.url("https://api.example.com/data/1") // 示例 URL
.get() // 显式声明 GET 方法(可省略,默认就是 GET)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Response Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
3. 发送post请求
```java
OkHttpClient client = new OkHttpClient();
// 构建 JSON 请求体
String json = "{\"name\":\"John\", \"age\":30}";
RequestBody body = RequestBody.create(json, MediaType.parse("application/json"));
// 构建 POST 请求
Request request = new Request.Builder()
.url("https://api.example.com/data")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Response Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
4. 发送put请求
```java
OkHttpClient client = new OkHttpClient();
// 构建表单请求体(或使用 JSON)
RequestBody body = new FormBody.Builder()
.add("id", "1")
.add("status", "updated")
.build();
// 构建 PUT 请求
Request request = new Request.Builder()
.url("https://api.example.com/data/1")
.put(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Response Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
5. 发送delete请求
```java
OkHttpClient client = new OkHttpClient();
// 构建 DELETE 请求(默认不带请求体)
Request request = new Request.Builder()
.url("https://api.example.com/data/1")
.delete()
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println("Response Code: " + response.code());
System.out.println("Response Body: " + response.body().string());
}
```
三,扩展功能
3.1 添加请求头
@Test
public void testAddHeader(){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.github.com/users/octocat")
.addHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")
.addHeader("User-Agent", "YourAppName")
.build();
}
3.2 设置请求超时
@Test
public void testAddTimeout(){
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.writeTimeout(10, TimeUnit.SECONDS) // 写超时
.readTimeout(30, TimeUnit.SECONDS) // 读超时
.build();
}
3.3 添加拦截器
拦截器可以用来修改请求和响应,或执行其他操作,比如日志记录或认证:
@Test
public void testAddInterceptor(){
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 打印请求信息
// 在请求之前添加日志
System.out.println("Sending request to " + request.url());
Response response = chain.proceed(request);
// 打印响应信息
// 在响应之后添加日志
System.out.println("Received response from " + response.request().url());
return response;
}
})
.build();
}
3.4 使用Cookie
默认情况下,OkHttp 会自动处理 Cookie。当你使用 OkHttpClient 进行请求时,OkHttp 会处理从服务器响应中接收到的 Cookie,并在后续的请求中自动发送这些 Cookie。这是通过 CookieJar 来实现的
如果需要自定义 Cookie 处理逻辑,可以实现 CookieJar 接口。CookieJar 接口有两个主要方法:
saveFromResponse(HttpUrl url, List<Cookie> cookies):保存从服务器响应中获得的 Cookies。loadForRequest(HttpUrl url):加载与请求 URL 相关的 Cookies
@Test
public void testCustomCookie(){
// 创建一个存储 Cookie 的集合
Set<Cookie> cookieStore = new CopyOnWriteArraySet<>();
// 创建自定义的 CookieJar
CookieJar cookieJar = new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.addAll(cookies); // 保存 Cookie
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return new ArrayList<>(cookieStore); // 加载 Cookie
}
};
// 创建 OkHttpClient 并设置自定义的 CookieJar
OkHttpClient client = new OkHttpClient.Builder()
.cookieJar(cookieJar)
.build();
}
3.5 处理缓存
OkHttp 支持 HTTP 缓存来减少网络流量和提高性能:要启用缓存,需要创建一个 Cache 对象,并将其配置到 OkHttpClient 中。Cache 对象用于存储和管理 HTTP 响应的缓存数据。
@Test
public void testUseCache(){
// 创建缓存目录和缓存大小
File cacheDirectory = new File("cache_directory");
Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024); // 10 MB
// 创建 OkHttpClient 并设置缓存
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
}
也可以配合设置缓存类型的header达到控制缓存,以下是缓存常用header
Cache-Control: max-age=<seconds>:指定资源可以在客户端缓存的最大时间。Cache-Control: no-cache:指示客户端每次请求都要重新验证资源。Cache-Control: no-store:指示客户端和服务器都不应缓存该资源。Expires: <date>:指定资源的过期时间。
3.6 使用Multipart请求
使用 Multipart 请求通常用于上传文件和发送表单数据。Multipart 请求允许你将文件和其他数据组合成一个 HTTP 请求,并发送到服务器。
Multipart 请求的基本概念
Multipart 请求通常使用 multipart/form-data 编码类型。这种请求方式允许你将多个部分(每部分可以是文件、文本或其他类型的数据)组合成一个请求体。每个部分都有自己的头部,描述该部分的数据类型和其他元数据。
创建 Multipart 请求
在 OkHttp 中,你可以使用 MultipartBody 类来构造 Multipart 请求体。MultipartBody 提供了 Builder 类,可以帮助你构建包含多个部分的请求体。
创建和发送一个简单 Multipart 请求的步骤:
- 创建 MultipartBody.Builder:用于构建
MultipartBody对象。 - 添加表单字段:使用
addFormDataPart方法添加表单字段。 - 添加文件字段:使用
addFormDataPart方法添加文件字段。 - 构建 RequestBody:将
MultipartBody构建成RequestBody。 - 构建请求:创建一个
Request对象,将RequestBody设置为请求体。 - 发送请求:使用
OkHttpClient发送请求。
public class MultipartRequestExample {
public static void main(String[] args) {
// 创建 OkHttpClient 实例
OkHttpClient client = new OkHttpClient();
// 创建 MultipartBody.Builder
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM) // 设置请求类型为 form-data
.addFormDataPart("username", "example_user") // 添加表单字段
.addFormDataPart("password", "example_password");
// 添加文件字段
File file = new File("path/to/your/file.txt");
RequestBody fileBody = RequestBody.create(file, MediaType.parse("text/plain"));
builder.addFormDataPart("file", file.getName(), fileBody);
// 创建 RequestBody
RequestBody requestBody = builder.build();
// 创建 Request
Request request = new Request.Builder()
.url("https://your-api-endpoint.com/upload")
.post(requestBody) // 使用 POST 方法发送请求
.build();
// 发送请求并处理响应
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 打印响应体
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.7 配置连接池
在 OkHttp 中,连接池是通过 ConnectionPool 类来管理的。你可以创建一个自定义的 ConnectionPool 实例,并将其配置到 OkHttpClient 中。
// 创建自定义连接池
ConnectionPool connectionPool = new ConnectionPool(
5, // 最大空闲连接数
5, // 连接保持时间
TimeUnit.MINUTES // 时间单位
);
@Test
public void testConnectionPool(){
// 创建 OkHttpClient 并设置自定义的连接池
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.build();
}
3.8 失败重连策略
要在 OkHttp 中实现失败重连策略,你可以通过自定义 Interceptor 实现。拦截器允许你在请求和响应的过程中执行自定义操作,包括重试逻辑。
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.IOException;
public class RetryInterceptor implements Interceptor {
private final int maxRetries;
public RetryInterceptor(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
int tryCount = 0;
while (tryCount < maxRetries) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
} catch (IOException e) {
// 如果是网络异常,则进行重试
if (tryCount >= maxRetries - 1) {
throw e; // 达到最大重试次数,抛出异常
}
}
tryCount++;
}
throw new IOException("Failed after " + maxRetries + " retries");
}
public static void main(String[] args) {
// 创建 OkHttpClient 并设置重试拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new RetryInterceptor(3)) // 最大重试次数为 3
.build();
Request request = new Request.Builder()
.url("https://api.github.com/users/octocat")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 打印响应体
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
不同的应用场景可能需要不同的重试策略。以下是一些常见的重试策略:
- 固定间隔重试:在每次重试之间使用固定的时间间隔。例如,每次失败后等待 1 秒再重试:
- 指数退避重试:每次重试之间的等待时间逐渐增加。例如,第一次重试等待 1 秒,第二次等待 2 秒,第三次等待 4 秒等:
- 只在特定异常情况下重试:可以根据异常类型或 HTTP 状态码决定是否重试。例如,只在网络异常或服务器错误(5xx 状态码)时重试:
3.9 异步请求
OkHttp 通过 enqueue(Callback) 方法实现异步请求,所有网络操作在后台线程池中执行,结果通过回调接口返回。
3.9.1 基础异步请求示例
import okhttp3.*;
public class AsyncExample {
public static void main(String[] args) {
OkHttpClient client = new OkHttpClient();
// 构建请求(以 GET 为例)
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
// 异步发送请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
// 注意:回调在 OkHttp 的线程池中执行,非主线程!
try {
if (response.isSuccessful()) {
String result = response.body().string();
System.out.println("请求成功: " + result);
} else {
System.err.println("请求失败,状态码: " + response.code());
}
} finally {
response.close(); // 手动关闭响应资源
}
}
@Override
public void onFailure(Call call, IOException e) {
// 网络错误、超时等异常
System.err.println("请求失败: " + e.getMessage());
}
});
// 主线程继续执行其他逻辑(非阻塞)
System.out.println("已发起异步请求,主线程继续运行...");
}
}
3.9.2 关键注意事项
-
回调线程特性
-
回调在 OkHttp 的线程池中执行,与主线程(Java 应用的启动线程)是分离的。
-
如果需要在回调中操作共享数据(如全局变量),需使用线程安全机制(如
synchronized、Lock)。
-
-
资源释放
- 异步请求的
Response不会自动关闭,必须手动调用response.close(),否则会导致资源泄漏。
- 异步请求的
3. 取消请求
-
通过
Call.cancel()终止请求(例如,用户取消操作或超时管理):Call call = client.newCall(request); call.enqueue(new Callback() { ... }); // 在需要时取消请求 call.cancel();
3.9.3 自定义线程池
import okhttp3.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CustomThreadPoolExample {
public static void main(String[] args) {
// 创建自定义线程池(例如固定大小为 5)
ExecutorService executor = Executors.newFixedThreadPool(5);
// 使用自定义线程池构建 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.dispatcher(new Dispatcher(executor))
.build();
// 发送异步请求(使用自定义线程池)
Request request = new Request.Builder()
.url("https://api.example.com/data")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
System.out.println("请求成功: " + response.body().string());
} finally {
response.close();
}
}
@Override
public void onFailure(Call call, IOException e) {
System.err.println("请求失败: " + e.getMessage());
}
});
}
}
3.9.4 在异步请求中处理返回结果
方案1:使用 CompletableFuture 包装异步结果
import okhttp3.*;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class AsyncResultHandler {
private static final OkHttpClient client = new OkHttpClient();
/**
* 发起异步 GET 请求,返回 CompletableFuture 包装的结果
* @param url 请求地址
* @return CompletableFuture<String> 异步结果
*/
public static CompletableFuture<String> asyncGet(String url) {
CompletableFuture<String> future = new CompletableFuture<>();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
if (response.isSuccessful()) {
// 将结果传递给 future
future.complete(body.string());
} else {
// 传递异常
future.completeExceptionally(new IOException("HTTP 错误码: " + response.code()));
}
}
}
@Override
public void onFailure(Call call, IOException e) {
// 传递网络错误
future.completeExceptionally(e);
}
});
return future;
}
public static void main(String[] args) {
try {
// 发起异步请求并获取 CompletableFuture
CompletableFuture<String> future = asyncGet("https://api.example.com/data");
// 设置超时时间(可选)
String result = future.get(5, TimeUnit.SECONDS); // 阻塞等待结果(最多 5 秒)
// 处理成功结果
System.out.println("异步请求结果: " + result);
} catch (InterruptedException e) {
System.err.println("请求被中断: " + e.getMessage());
} catch (ExecutionException e) {
System.err.println("请求执行异常: " + e.getCause().getMessage());
} catch (TimeoutException e) {
System.err.println("请求超时: " + e.getMessage());
}
}
}
方案2:自定义回调接口
import okhttp3.*;
import java.io.IOException;
public class CallbackHandlerExample {
// 自定义回调接口
public interface ResultCallback {
void onSuccess(String result);
void onFailure(Throwable error);
}
private static final OkHttpClient client = new OkHttpClient();
/**
* 发起异步 GET 请求,通过回调返回结果
* @param url 请求地址
* @param callback 自定义回调接口
*/
public static void asyncGetWithCallback(String url, ResultCallback callback) {
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
if (response.isSuccessful()) {
callback.onSuccess(body.string());
} else {
callback.onFailure(new IOException("HTTP 错误码: " + response.code()));
}
}
}
@Override
public void onFailure(Call call, IOException e) {
callback.onFailure(e);
}
});
}
public static void main(String[] args) {
// 调用异步请求并处理结果
asyncGetWithCallback("https://api.example.com/data", new ResultCallback() {
@Override
public void onSuccess(String result) {
System.out.println("异步请求成功: " + result);
// 可以在这里触发后续业务逻辑
}
@Override
public void onFailure(Throwable error) {
System.err.println("异步请求失败: " + error.getMessage());
// 可以在这里处理错误(如重试、记录日志等)
}
});
// 主线程继续执行其他任务(非阻塞)
System.out.println("主线程继续运行...");
// 防止程序提前退出(模拟长时间运行的应用)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
方案3:处理多个异步请求
import okhttp3.*;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.ArrayList;
public class MultipleAsyncRequests {
private static final OkHttpClient client = new OkHttpClient();
/**
* 并行发起多个异步请求,合并结果
*/
public static void main(String[] args) {
List<String> urls = List.of(
"https://api.example.com/data/1",
"https://api.example.com/data/2",
"https://api.example.com/data/3"
);
List<CompletableFuture<String>> futures = new ArrayList<>();
// 发起所有异步请求
for (String url : urls) {
CompletableFuture<String> future = new CompletableFuture<>();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
if (response.isSuccessful()) {
future.complete(body.string());
} else {
future.completeExceptionally(new IOException("HTTP 错误码: " + response.code()));
}
}
}
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
});
futures.add(future);
}
// 等待所有请求完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
);
// 处理最终结果
allFutures.thenRun(() -> {
for (int i = 0; i < futures.size(); i++) {
CompletableFuture<String> future = futures.get(i);
try {
String result = future.get();
System.out.println("请求 " + urls.get(i) + " 结果: " + result);
} catch (InterruptedException | ExecutionException e) {
System.err.println("请求 " + urls.get(i) + " 失败: " + e.getCause().getMessage());
}
}
}).join(); // 阻塞主线程等待所有结果(仅示例,生产环境应避免)
}
}