OkHttp 客户端从入门到入土
故事开端
- 南瓜:如果我有一个接口的authorization,我想要获取这个接口的数据,那就配置HttpClient,然后创建一个实例发送http请求获取那里面的数据吗
- 天天:那你就是先调用这个获取authorization的接口 把里边的内容放到请求头里,配一个全局的,然后再去请求别的方法就好了,不过这个请求头一般都会有时间限制
- 南瓜:emmm,是要在httpxlient里面调用吗
- 天天:可以的,你是用okhttpclient还是什么
- 南瓜:我不知道,我什么都不知道……
在移动应用和后端服务开发中,网络请求是不可或缺的一部分
选择一个强大、易用且高效的网络请求库至关重要
OkHttp 凭借其出色的性能、简洁的 API 和丰富的功能,成为了 Android 和 Java 开发者的首选
OkHttp 简介
OkHttp 是 Square 公司开源的一个现代化的 HTTP 客户端,专注于高效和可靠的网络通信。它支持 HTTP/2、连接池、透明的 GZIP 压缩、请求重定向、拦截器和 WebSocket 等功能,能够满足各种复杂的网络请求场景。
OkHttp 的主要特点:
- 性能: 连接池复用 TCP 连接,减少握手延迟;高效的请求调度机制,优化并发性能
- 可靠性: 连接失败自动重试,透明的 GZIP 压缩,减少网络传输大小
- 易用性: 简洁的 API,使用 Fluent 风格的构建器模式,易于上手
- 丰富的功能: 支持 HTTP/2, WebSocket, 连接池, 请求拦截, 响应拦截, 缓存等
基本使用
添加 OkHttp 依赖
1 | <dependency> |
如果使用 Gradle,则添加以下依赖:1
implementation 'com.squareup.okhttp3:okhttp:4.12.0' // 请使用最新版本
创建 OkHttpClient
OkHttp 的核心是 OkHttpClient,它负责发送和接收 HTTP 请求
你可以使用 OkHttpClient.Builder 来配置 OkHttpClient1
2
3
4import okhttp3.OkHttpClient;
OkHttpClient client = new OkHttpClient.Builder()
.build();
最简单的 OkHttpClient 创建方式就是上面的代码。OkHttpClient.Builder 提供了多种配置选项,例如设置连接超时时间、读取超时时间、写入超时时间、拦截器、连接池等等
发送 GET 请求
发送 GET 请求非常简单,首先创建一个 Request 对象,然后调用 OkHttpClient.newCall() 方法执行请求1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import okhttp3.*;
import java.io.IOException;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().build();
// 创建一个 Request 对象
Request request = new Request.Builder()
.url("https://www.example.com/api/data")
.build();
// 使用 client 发送请求,并得到 Response 对象
try (Response response = client.newCall(request).execute()) {
// 检查请求是否成功
if (response.isSuccessful()) {
// 获取响应体并转换成字符串
String body = response.body().string();
System.out.println("Response body: " + body);
} else {
// 输出错误信息
System.out.println("Request failed, code: " + response.code() + ", message: " + response.message());
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
使用 Request.Builder 创建 Request 对象,指定请求 URL
调用 client.newCall(request).execute() 发送请求并同步获取响应
使用 response.isSuccessful() 判断请求是否成功(状态码在 200 - 299 之间)
使用 response.body().string() 获取响应体的内容(注意:只能调用一次)
发送 POST 请求
发送 POST 请求需要创建一个 RequestBody 对象,并将其添加到 Request 中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import okhttp3.*;
import java.io.IOException;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().build();
MediaType mediaType = MediaType.parse("application/json");
// 创建 RequestBody
RequestBody body = RequestBody.create(mediaType,"{\"name\":\"John\", \"age\": 30}");
// 创建 Request 对象,设置方法为 POST,并添加 RequestBody
Request request = new Request.Builder()
.url("https://www.example.com/api/users")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
// 检查请求是否成功
if (response.isSuccessful()) {
// 获取响应体并转换成字符串
String responseBody = response.body().string();
System.out.println("Response body: " + responseBody);
} else {
// 输出错误信息
System.out.println("Request failed, code: " + response.code() + ", message: " + response.message());
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
创建 MediaType 对象,指定请求体的类型(这里是 JSON)
使用 RequestBody.create() 创建 RequestBody 对象,传入 MediaType 和请求体内容
在 Request.Builder 中使用 post(body) 方法设置请求方法为 POST,并添加 RequestBody
发送带有参数的请求
可以通过 FormBody 或 HttpUrl.Builder 构造带有参数的请求
FormBody 适用于 application/x-www-form-urlencoded 格式的数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import okhttp3.*;
import java.io.IOException;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().build();
// 使用 FormBody.Builder 构建请求体
RequestBody body = new FormBody.Builder()
.add("name", "John")
.add("age", "30")
.build();
// 创建 Request 对象
Request request = new Request.Builder()
.url("https://www.example.com/api/users")
.post(body) // 使用 post 方法
.build();
try (Response response = client.newCall(request).execute()) {
// 检查请求是否成功
if (response.isSuccessful()) {
// 获取响应体并转换成字符串
String responseBody = response.body().string();
System.out.println("Response body: " + responseBody);
} else {
// 输出错误信息
System.out.println("Request failed, code: " + response.code() + ", message: " + response.message());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
HttpUrl.Builder 适用于将参数拼接到 URL 上1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import okhttp3.*;
import java.io.IOException;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().build();
// 创建 HttpUrl.Builder
HttpUrl.Builder urlBuilder = HttpUrl.parse("https://www.example.com/api/users")
.newBuilder()
.addQueryParameter("name", "John")
.addQueryParameter("age", "30");
// 创建 Request 对象
Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
try (Response response = client.newCall(request).execute()) {
// 检查请求是否成功
if (response.isSuccessful()) {
// 获取响应体并转换成字符串
String responseBody = response.body().string();
System.out.println("Response body: " + responseBody);
} else {
// 输出错误信息
System.out.println("Request failed, code: " + response.code() + ", message: " + response.message());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
异步请求
为了避免阻塞主线程,OkHttp 提供了异步请求的方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import okhttp3.*;
import java.io.IOException;
public class OkHttpDemo {
public static void main(String[] args) {
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url("https://www.example.com/api/data")
.build();
// 创建Call对象
Call call = client.newCall(request);
// 执行异步请求
call.enqueue(new Callback() {
public void onFailure(Call call, IOException e) {
// 请求失败的回调
System.out.println("Request failed: " + e.getMessage());
}
public void onResponse(Call call, Response response) throws IOException {
// 请求成功的回调
if (response.isSuccessful()) {
String responseBody = response.body().string();
System.out.println("Response body: " + responseBody);
} else {
System.out.println("Request failed, code: " + response.code() + ", message: " + response.message());
}
}
});
}
}
调用 client.newCall(request) 获取一个 Call 对象
使用 call.enqueue() 执行异步请求,传入一个 Callback 对象
Callback 接口包含 onFailure() 和 onResponse() 方法,分别处理请求失败和成功的情况高级特性
拦截器
拦截器(Interceptor)允许你在请求发送前和响应接收后拦截请求和响应,可以用于添加公共 header、日志记录、缓存、重定向等功能
添加拦截器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 import okhttp3.*;
import okio.Buffer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
// 创建一个日志拦截器
Interceptor logInterceptor = new Interceptor() {
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
System.out.println("Sending request " + request.url() + " " + request.method());
if(request.body()!=null){
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
System.out.println("Request body: " + buffer.readString(StandardCharsets.UTF_8));
}
Response response = chain.proceed(request);
long t2 = System.nanoTime();
double duration = (t2 - t1) / 1e6d;
System.out.println("Received response for " + response.request().url() + " " + response.code() + " in " + duration + "ms");
if (response.body() != null) {
ResponseBody responseBody = response.peekBody(Long.MAX_VALUE);
System.out.println("Response body: " + responseBody.string());
}
return response;
}
};
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logInterceptor) // 添加日志拦截器
.build();
Request request = new Request.Builder()
.url("https://www.example.com/api/data")
.build();
try(Response response = client.newCall(request).execute()){
if(response.isSuccessful()){
System.out.println("Response body: " + response.body().string());
}
}
}
}
创建一个实现了 Interceptor 接口的匿名内部类
在 intercept() 方法中,可以拦截请求和响应
调用 chain.proceed(request) 继续执行请求
使用 OkHttpClient.Builder.addInterceptor() 添加拦截器
连接池
OkHttp 默认启用了连接池,它会复用 TCP 连接,减少握手次数,从而提高性能。可以通过 OkHttpClient.Builder 配置连接池1
2
3
4
5
6
7import okhttp3.*;
import java.util.concurrent.TimeUnit;
ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(connectionPool)
.build();
创建一个 ConnectionPool 对象,指定最大连接数、空闲连接存活时间等参数
使用 OkHttpClient.Builder.connectionPool() 方法配置连接池
缓存
OkHttp 支持 HTTP 缓存,可以减少网络请求次数,提高性能,可以通过 Cache 配置缓存1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import okhttp3.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class OkHttpDemo {
public static void main(String[] args) throws IOException {
Path path = Files.createTempDirectory("okhttp-cache");
File cacheDir = path.toFile();
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 设置缓存目录和大小
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache) // 配置缓存
.build();
Request request = new Request.Builder()
.url("https://www.example.com/api/data")
.build();
try (Response response = client.newCall(request).execute()){
if(response.isSuccessful()){
System.out.println("Response body: " + response.body().string());
}
}
}
}
创建 Cache 对象,指定缓存目录和最大缓存大小
使用 OkHttpClient.Builder.cache() 方法配置缓存
超时设置
OkHttp 提供了多种超时设置,包括连接超时、读取超时和写入超时:
import okhttp3.*;
import java.time.Duration;
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(10)) // 连接超时时间设置为 10 秒
.readTimeout(Duration.ofSeconds(20)) // 读取超时时间设置为 20 秒
.writeTimeout(Duration.ofSeconds(20)) //写入超时时间设置为 20 秒
.build();
OkHttp 的最佳实践
使用连接池: 尽可能复用 TCP 连接,减少握手延迟。
启用 HTTP 缓存: 减少网络请求次数,提高性能。
使用拦截器: 添加公共头信息、日志记录等功能,提高代码复用性。
设置合适的超时时间: 避免请求无响应。
异步处理请求: 避免阻塞主线程。
使用最新版本的 OkHttp: 享受最新的功能和性能优化。
使用 try-with-resources: 确保响应体的资源被正确关闭。
常见问题和解决方案
SSLHandshakeException: 证书验证失败,需要信任服务器证书或使用证书固定。
连接超时: 设置更大的超时时间,检查网络状况。
请求被取消: 调用 Call.cancel() 取消请求。
响应体读取多次: 读取响应体只能读取一次。如果需要多次读取,请使用 response.peekBody() 创建响应体的副本。
内存泄露: 在使用 call.enqueue() 执行异步请求时,确保你的 Callback 没有持有外部对象的强引用,防止内存泄漏。