故事开端

  • 南瓜:如果我有一个接口的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
2
3
4
5
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version> <!-- 请使用最新版本 -->
</dependency>

如果使用 Gradle,则添加以下依赖:

1
implementation 'com.squareup.okhttp3:okhttp:4.12.0' // 请使用最新版本

笨蛋小贴士

  1. Gradle 是什么?
    Gradle 是一个开源的构建自动化工具,广泛应用于软件开发领域,尤其在 Java、Android 和 Kotlin 项目中非常流行
    它可以自动化编译、测试、打包和部署等构建过程,让开发者专注于编写代码,而无需手动处理复杂的构建任务
    implementation ‘com.squareup.okhttp3:okhttp:4.12.0’
    这行代码是 Gradle 构建脚本中的一个依赖声明,它的含义如下:

    • implementation: 表示该依赖仅在编译时和运行时需要,不会传递给其他模块。
    • com.squareup.okhttp3:okhttp: 表示依赖库的 Maven 坐标,其中:
      • com.squareup.okhttp3:是 groupId,表示该库的组织或公司。
      • okhttp:是 artifactId,表示该库的名称。
    • 4.12.0: 表示依赖库的版本号

创建 OkHttpClient

OkHttp 的核心是 OkHttpClient,它负责发送和接收 HTTP 请求
你可以使用 OkHttpClient.Builder 来配置 OkHttpClient

1
2
3
4
import 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
30
import 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
34
import 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
36
import 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
35
import 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
34
import 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() {
@Override
public void onFailure(Call call, IOException e) {
// 请求失败的回调
System.out.println("Request failed: " + e.getMessage());
}

@Override
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() {
@Override
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
7
import 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
29
import 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 没有持有外部对象的强引用,防止内存泄漏。