Featured image of post OkHttp 阅读解析

OkHttp 阅读解析

让 Gimini 生成了一篇关于解析 okhttp 的文章。辅助阅读源码


OkHttp 4.0+ 最新版本解析

1. 引言

本文档旨在为读者提供 OkHttp 4.0+ 最新版本的全面解析。OkHttp 是 Square 公司开发的高效 HTTP 客户端,因其强大的功能、简洁的 API、卓越的性能以及对现代 HTTP 协议(如 HTTP/2 和 WebSocket)的良好支持,在 Android 和 Java 项目中得到了广泛应用。自 4.0 版本起,OkHttp 采用了 Kotlin 优先的开发策略,同时保持了与 Java 的良好兼容性,并引入了诸多新特性与改进。本解析旨在阐明 OkHttp 的运作机制,并详细介绍其核心组件与功能,以期提升读者对该强大网络库的理解与应用能力。

2. 核心工作原理

本节将概述 OkHttp 的核心工作原理。对这些基础概念的理解对于 OkHttp 的有效运用至关重要。OkHttp 处理 HTTP 请求的核心流程可概括为“构建请求 → 执行请求 → 处理响应”,并通过“拦截器链”实现了高度的可扩展性。以下为具体步骤的详细阐述:

  1. 构建请求 (Building Request): 需利用 Request.Builder 构建一个不可变的 Request 对象,其中包含 URL、请求方法、请求头以及请求体等必要信息。
  2. 创建客户端 (Creating Client): 随后,通过 OkHttpClient.Builder 配置并实例化一个 OkHttpClient 对象。此实例在 OkHttp 架构中至关重要,负责管理连接池、缓存、超时设置以及拦截器链等全局网络配置。
  3. 发起调用 (Making Call): OkHttpClient 通过 newCall(Request) 方法生成一个 Call 对象。Call 代表一个待执行的 HTTP 请求实例。
  4. 执行请求 (Executing Request): Call 对象可支持同步 (execute()) 或异步 (enqueue()) 两种执行模式。
    • 同步执行 (execute()): 请求发出后,当前线程将保持阻塞状态,直至接收到服务器响应或操作异常终止。
    • 异步执行 (enqueue()): 请求在后台线程池中执行,其结果将通过 Callback 回调函数进行通知。
  5. 拦截器链 (Interceptor Chain): 请求在被实际发送至网络之前,将依次通过由多个 Interceptor(拦截器)构成的链条。每个拦截器均可执行请求修改、重定向处理、响应缓存、认证信息添加以及日志记录等操作。当响应从网络返回时,它将沿此链条反向传播,允许各拦截器对响应进行后处理。
  6. 处理响应 (Handling Response): 请求执行完毕后,将返回一个不可变的 Response 对象,该对象封装了响应状态码、响应头以及响应体等所有相关信息。

3. 关键类和函数

本节将详细阐述 OkHttp 库中的关键类及其功能。

3.1 OkHttpClient

作用: OkHttpClient 是 OkHttp 的核心客户端,负责执行 HTTP 请求,并管理连接、缓存、认证、超时以及拦截器链等各项网络操作。它是所有 HTTP 请求的发起入口。

特性:

  • 不可变性: OkHttpClient 实例一旦构建完成,其配置即不可更改。此特性确保了其在多线程环境下的操作安全性。
  • 高度可配置性: 通过 OkHttpClient.Builder,用户可以对连接超时、读取超时、写入超时、拦截器、连接池、缓存和代理等多种参数进行精细配置。
  • 实例复用推荐: 建议在应用程序的整个生命周期中仅创建并复用一个 OkHttpClient 实例。此策略能够有效提升性能并节约资源,因客户端内部维护着连接池和线程池。

常用函数 (通过 Builder 配置):

  • connectTimeout(long timeout, TimeUnit unit): 设置连接建立的超时时间。
  • readTimeout(long timeout, TimeUnit unit): 设置从服务器读取数据流的超时时间。
  • writeTimeout(long timeout, TimeUnit unit): 设置向服务器写入数据流的超时时间。
  • addInterceptor(Interceptor interceptor): 添加应用层拦截器,用于处理每次HTTP请求。
  • addNetworkInterceptor(Interceptor interceptor): 添加网络层拦截器,其作用更贴近网络通信,可用于观察实际发送的网络请求和接收的原始响应。
  • cache(Cache cache): 配置HTTP缓存机制。
  • connectionPool(ConnectionPool connectionPool): 设置连接池管理策略。
  • cookieJar(CookieJar cookieJar): 配置Cookie管理接口。
  • sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager): 配置HTTPS连接的安全套接字工厂和信任管理器。
  • proxy(Proxy proxy): 设置网络代理服务器。
  • dispatcher(Dispatcher dispatcher): 配置请求调度器,用于控制异步请求的并发处理。
  • build(): 构建并返回一个 OkHttpClient 实例。

3.2 Request

作用: Request 对象代表一个待发送至服务器的 HTTP 请求。它是一个不可变的数据结构。

特性:

  • 不可变性: Request 实例一旦构建,其内部状态不可直接修改。任何对请求的调整均需通过 Request.Builder 创建新的 Request 实例。
  • 请求信息封装: 完整封装了请求的URL、HTTP方法、请求头以及请求体等所有相关信息。

常用函数 (通过 Builder 构建):

  • url(String url) / url(HttpUrl url): 设置请求的目标URL。
  • method(String method, RequestBody body): 设置HTTP请求方法(如 “GET”、“POST”)并关联请求体(GET方法通常为 null)。
  • get(), post(RequestBody body), put(RequestBody body), delete(RequestBody body), head(): 提供便捷方法以配置常见的HTTP请求方法。
  • header(String name, String value): 设置一个请求头。若存在同名请求头,则覆盖其现有值。
  • addHeader(String name, String value): 添加一个请求头,不覆盖已存在的同名请求头。
  • removeHeader(String name): 移除所有具有指定名称的请求头。
  • build(): 构建并返回 Request 实例。

获取信息函数:

  • url(): 获取请求的 HttpUrl 对象。
  • method(): 获取请求的HTTP方法。
  • headers(): 获取请求头 Headers 对象。
  • body(): 获取请求体 RequestBody。

3.3 RequestBody

作用: RequestBody 是一个抽象类,用于表示HTTP请求中的请求体,主要应用于POST、PUT等需要携带数据体的请求。

特性:

  • 内容类型声明: 必须通过 MediaType 指定请求体的数据类型,以告知服务器其内容格式。
  • 流式写入: 请求体的内容通过 writeTo(BufferedSink sink) 方法以流的形式进行写入。

常用创建方法 (通过静态 create 方法):

  • RequestBody.create(String content, MediaType mediaType): 从字符串创建请求体(常用)。
  • RequestBody.create(byte[] content, MediaType mediaType): 从字节数组创建请求体。
  • RequestBody.create(File file, MediaType mediaType): 从文件创建请求体(常用,用于文件上传)。
  • RequestBody.create(ByteString content, MediaType mediaType): 从 okio.ByteString 创建请求体。

具体实现类:

  • FormBody:
    • 功能: 用于构建并发送 application/x-www-form-urlencoded 形式的表单数据,即键值对形式的数据。
    • 构建示例: new FormBody.Builder().add(“key”, “value”).add(“key2”, “value2”).build();
  • MultipartBody:
    • 功能: 用于构建并发送 multipart/form-data 形式的表单数据,广泛应用于文件上传场景,可同时包含多个文件及普通表单字段。
    • 构建示例: new MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart(“name”, “value”).addPart(MultipartBody.Part.createFormData(“file”, “filename.txt”, RequestBody.create(file, MediaType.parse(“image/jpeg”)))).build();

3.4 Response

作用: Response 对象代表从服务器接收到的 HTTP 响应。它是一个不可变的数据结构。

特性:

  • 不可变性: Response 实例一旦接收,其内容不可被修改。
  • 响应信息封装: 完整封装了响应的状态码、响应头、响应体以及协议版本等所有相关信息。

常用函数:

  • request(): 获取生成此响应的 Request 对象。
  • code(): 获取HTTP状态码(例如 200, 404, 500)。
  • message(): 获取HTTP状态消息(例如 “OK”, “Not Found”)。
  • headers(): 获取响应头 Headers 对象。
  • body(): 获取响应体 ResponseBody。
  • isSuccessful(): 判断响应状态码是否属于 200-299 范围内的成功状态。
  • isRedirect(): 判断响应是否为重定向类型。
  • protocol(): 获取当前使用的HTTP协议版本。

3.5 ResponseBody

作用: ResponseBody 代表HTTP响应中的响应体部分。通常通过 Response.body() 方法获取。

特性:

  • 单次读取限制: ResponseBody 只能被读取一次。一旦其内容被读取(例如通过 string(), bytes(), byteStream() 方法),底层的输入流将关闭,无法再次读取。
  • 资源管理: ResponseBody 实例在使用完毕后必须进行关闭,通常建议利用 try-with-resources 语句或显式调用 close() 方法。

常用函数:

  • string(): 将响应体内容读取并转换为字符串(常用)。需要注意的是,此操作会将整个响应体加载至内存,对于大型文件不推荐使用。
  • bytes(): 将响应体内容读取并转换为字节数组。
  • byteStream(): 获取响应体的输入流 InputStream,适用于对大型文件进行流式读取。
  • charStream(): 获取响应体的字符流 Reader。
  • contentType(): 获取响应体的 MediaType。
  • contentLength(): 获取响应体的长度。

3.6 Call

作用: Call 对象代表一个可执行的 HTTP 请求实例。它通过 OkHttpClient.newCall(Request) 方法进行创建。

特性:

  • 可取消性: 正在进行的请求可以通过调用 cancel() 方法进行中止。
  • 一次性执行: Call 实例仅能被执行一次(无论是同步还是异步)。若需重新尝试请求,必须创建新的 Call 实例。

常用函数:

  • execute(): 同步执行请求。此方法将阻塞当前线程,直至接收到响应或抛出异常。
    try (Response response = client.newCall(request).execute()) {
    if (response.isSuccessful()) {
    System.out.println(Objects.requireNonNull(response.body()).string());
    } else {
    System.err.println(“Request failed: " + response.code());
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

  • enqueue(Callback responseCallback): 异步执行请求。请求将在后台线程池中执行,其结果通过提供的 Callback 接口进行回调。
    client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {
    e.printStackTrace();
    }

    @Override  
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {  
        try (ResponseBody responseBody \= response.body()) {  
            if (response.isSuccessful()) {  
                System.out.println(Objects.requireNonNull(responseBody).string());  
            } else {  
                System.err.println("Request failed: " \+ response.code());  
            }  
        }  
    }  
    

    });

  • cancel(): 用于取消正在进行的请求。

3.7 Interceptor

作用: Interceptor 是 OkHttp 的核心功能之一,用于拦截和修改HTTP请求或响应。它们构成一个责任链,允许在请求的生命周期中插入自定义逻辑。

类型:

  • 应用拦截器 (Application Interceptors): 通过 OkHttpClient.Builder.addInterceptor() 方法添加。它们位于请求处理流程的顶层,可以对初始请求进行修改、处理重定向、管理缓存或执行重试逻辑。这类拦截器仅被调用一次,即使底层网络操作发生重定向或重试。
  • 网络拦截器 (Network Interceptors): 通过 OkHttpClient.Builder.addNetworkInterceptor() 方法添加。它们更接近实际的网络通信层,能够观察到发送至网络的原始请求以及从网络接收的原始响应。在重定向或重试等场景下,这类拦截器可能会被多次调用。

intercept(Chain chain) 函数:

所有拦截器均须实现此方法。

  • Chain chain: 提供对当前请求的访问,并负责将请求传递至拦截器链中的下一个组件。
  • chain.request(): 用于获取当前请求对象。
  • chain.proceed(Request request): 将(可能已修改的)请求传递至拦截器链中的下一个拦截器或最终的网络执行器,并阻塞直至接收到响应。

示例:

class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
System.out.printf(“Sending request %s on %s%n%s%n”,
request.url(), chain.connection(), request.headers());

    Response response \= chain.proceed(request); // 将请求传递给下一个处理器

    long t2 \= System.nanoTime();  
    System.out.printf("Received response for %s in %.1fms%n%s%n",  
            response.request().url(), (t2 \- t1) / 1e6d, response.headers());  
    return response;  
}  

}
// 添加至客户端示例:
// OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new LoggingInterceptor()).build();

3.8 HttpUrl

作用: HttpUrl 是一个专用于解析、构建和操作 URL 的类。它提供了比 java.net.URL 更为安全和健壮的URL处理机制。

特性:

  • 不可变性: HttpUrl 对象的所有操作均返回新的 HttpUrl 实例。
  • URL各部分解析: 提供了便捷的方法以访问URL的各个组成部分,例如路径段、查询参数及片段标识符等。

常用函数:

  • parse(String url): 从字符串解析URL。
  • newBuilder(): 获取一个 Builder 实例,用于构建或修改URL。
  • addPathSegment(String pathSegment): 添加路径段。
  • addQueryParameter(String name, String value): 添加查询参数。
  • build(): 构建 HttpUrl 实例。

3.9 Headers

作用: Headers 对象代表HTTP请求或响应中的头部信息,以键值对的形式存储。

特性:

  • 不可变性: Headers 实例一旦构建,其内容不可被修改。
  • 支持多值头部: 允许存在多个同名的头部字段。

常用函数 (通过 Builder 构建):

  • new Headers.Builder().add(“name”, “value”).build();
  • header(String name): 获取第一个具有指定名称的头部值。
  • values(String name): 获取所有具有指定名称的头部值列表。
  • toMultimap(): 将所有头部信息转换为 Map<String, List<String>> 格式。

3.10 CookieJar

作用: CookieJar 接口用于管理HTTP Cookie。在请求发送前,CookieJar 负责从存储中加载相关Cookie并将其添加至请求头;在接收到响应后,它则负责将响应中包含的 Set-Cookie 头信息保存到存储中。

默认实现:

  • OkHttp 默认不提供持久化的Cookie存储功能。用户需自行实现 CookieJar 接口或集成第三方库以实现Cookie的持久化管理。

3.11 ConnectionPool

作用: ConnectionPool 负责管理HTTP连接的复用。它维护一个活跃的HTTP连接池,从而减少建立新连接的开销,显著提升性能。

特性:

  • 默认启用: OkHttpClient 默认情况下会启用连接池。
  • 可配置性: 用户可对最大空闲连接数和连接保持时间进行配置。

3.12 Dispatcher

作用: Dispatcher 用于控制异步请求的并发策略。其内部维护两个请求队列:ready async calls(等待执行的异步请求)和 running async calls(正在执行的异步请求)。

特性:

  • 默认并发限制: 默认情况下,Dispatcher 最多同时执行 64 个异步请求,且针对每个主机的并发请求数限制为 5 个。
  • 可自定义: 用户可通过配置 maxRequests 和 maxRequestsPerHost 参数来自定义并发数量。

3.13 MediaType

作用: MediaType 代表HTTP中的Content-Type(MIME类型),例如 application/json、text/plain、image/jpeg 等。

特性:

  • 不可变性: MediaType 实例是不可变的。
  • 包含类型、子类型和参数: 完整封装了媒体类型、子类型及其相关参数。

常用函数:

  • MediaType.parse(String string): 从字符串解析 MediaType。

4. 工作流程示例

本节将通过一个具体的 Java 代码示例,演示 OkHttp 4.0+ 发送 POST 请求并处理 JSON 响应的完整工作流程。此示例旨在帮助读者理解客户端的初始化、请求的构建以及同步/异步响应的处理机制,从而将理论知识应用于实际开发场景。

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

import org.jetbrains.annotations.NotNull; // OkHttp 4.0+ 使用 Kotlin 优先,可能会有这个注解

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class OkHttpExample {

// 假设这是您的应用程序 ID,用于 Firestore 或其他需要唯一标识的地方  
private static final String appId \= typeof \_\_app\_id \!== 'undefined' ? \_\_app\_id : "default-app-id";

// JSON MediaType  
public static final MediaType JSON\_MEDIA\_TYPE \= MediaType.parse("application/json; charset=utf-8");

// 日志拦截器  
static class MyLoggingInterceptor implements Interceptor {  
    @NotNull  
    @Override  
    public Response intercept(@NotNull Chain chain) throws IOException {  
        Request request \= chain.request();  
        long t1 \= System.nanoTime();  
        System.out.printf("OkHttpExample \- 正在发送请求 %s %s...%n", request.method(), request.url());  
        System.out.println("请求头:\\n" \+ request.headers());  
        if (request.body() \!= null) {  
            // 注意:读取 RequestBody 的内容可能会消耗掉它,所以在实际拦截器中要小心  
            // 这里为了演示,假设请求体不大且可以重复读取  
            try (okhttp3.Buffer buffer \= new okhttp3.Buffer()) {  
                request.body().writeTo(buffer);  
                System.out.println("请求体: " \+ buffer.readUtf8());  
            } catch (Exception e) {  
                System.err.println("无法读取请求体: " \+ e.getMessage());  
            }  
        }

        Response response \= chain.proceed(request); // 将请求传递给下一个处理器

        long t2 \= System.nanoTime();  
        System.out.printf("OkHttpExample \- 收到响应 %s (耗时 %.1fms)%n",  
                response.request().url(), (t2 \- t1) / 1e6d);  
        System.out.println("响应状态码: " \+ response.code());  
        System.out.println("响应头:\\n" \+ response.headers());

        return response;  
    }  
}

public static void main(String\[\] args) {  
    // 1\. 创建 OkHttpClient 实例  
    // 建议在应用生命周期中复用一个 OkHttpClient 实例  
    OkHttpClient client \= new OkHttpClient.Builder()  
            .connectTimeout(10, TimeUnit.SECONDS) // 连接超时  
            .readTimeout(10, TimeUnit.SECONDS)    // 读取超时  
            .writeTimeout(10, TimeUnit.SECONDS)   // 写入超时  
            .addInterceptor(new MyLoggingInterceptor()) // 添加自定义日志拦截器  
            .build();

    // 目标 URL (使用一个公共的测试 API)  
    // 这个 API 应该能接受 POST 请求并返回 JSON  
    HttpUrl url \= Objects.requireNonNull(HttpUrl.parse("https://jsonplaceholder.typicode.com/posts"));

    // 2\. 构建 RequestBody (JSON 格式)  
    String jsonPayload \= "{\\"title\\": \\"foo\\", \\"body\\": \\"bar\\", \\"userId\\": 1}";  
    RequestBody requestBody \= RequestBody.create(jsonPayload, JSON\_MEDIA\_TYPE);

    // 3\. 构建 Request 实例  
    Request request \= new Request.Builder()  
            .url(url)  
            .post(requestBody) // 设置为 POST 方法,并关联请求体  
            .addHeader("User-Agent", "OkHttp-Example/" \+ appId) // 添加自定义请求头  
            .build();

    // 4\. 创建 Call 实例并执行  
    System.out.println("\\n--- 开始同步请求 \---");  
    try (Response response \= client.newCall(request).execute()) { // 同步执行  
        if (response.isSuccessful()) {  
            System.out.println("同步请求成功\! 响应体:\\n" \+ Objects.requireNonNull(response.body()).string());  
        } else {  
            System.err.println("同步请求失败\! 状态码: " \+ response.code() \+ ", 消息: " \+ response.message());  
            // 打印错误响应体(如果存在)  
            if (response.body() \!= null) {  
                System.err.println("错误响应体: " \+ response.body().string());  
            }  
        }  
    } catch (IOException e) {  
        System.err.println("同步请求发生 IO 错误: " \+ e.getMessage());  
        e.printStackTrace();  
    }

    System.out.println("\\n--- 开始异步请求 \---");  
    client.newCall(request).enqueue(new Callback() { // 异步执行  
        @Override  
        public void onFailure(@NotNull Call call, @NotNull IOException e) {  
            System.err.println("异步请求失败: " \+ e.getMessage());  
            e.printStackTrace();  
        }

        @Override  
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {  
            // 确保在使用 ResponseBody 之前检查它不为 null  
            try (ResponseBody responseBody \= response.body()) {  
                if (response.isSuccessful()) {  
                    System.out.println("异步请求成功\! 响应体:\\n" \+ Objects.requireNonNull(responseBody).string());  
                } else {  
                    System.err.println("异步请求失败\! 状态码: " \+ response.code() \+ ", 消息: " \+ response.message());  
                    if (responseBody \!= null) {  
                         System.err.println("错误响应体: " \+ responseBody.string());  
                    }  
                }  
            } finally {  
                // 确保 ResponseBody 被关闭,即使在发生异常时  
                // try-with-resources 会自动处理  
            }  
        }  
    });

    // 由于是异步请求,main 线程可能会先结束,所以这里等待一下  
    try {  
        Thread.sleep(5000); // 等待异步请求完成  
    } catch (InterruptedException e) {  
        Thread.currentThread().interrupt();  
    }  
    System.out.println("\\n--- 请求完成 \---");  
}  

}

5. 总结

综上所述,OkHttp 4.0+ 版本在继承其固有的卓越性能与便捷易用性的基础上,进一步优化了 API 设计与内部实现。其核心优势体现为:

  • 简洁的 API 设计: 通过建造者模式,请求与客户端的构建过程呈现出高度的直观性。
  • 强大的拦截器机制: 提供了高度的灵活性,能够便捷地实现日志记录、认证管理、缓存优化及请求重试等高级功能。
  • 高效的网络处理能力: 内置连接池、GZIP 压缩以及 HTTP/2 支持等特性,显著提升了网络通信效率。
  • 出色的健壮性: 具备自动处理网络故障及请求重定向等异常情况的能力。
  • 对现代协议的全面支持: 原生支持 HTTP/2 和 WebSocket 协议。
  • 对 Kotlin 的良好兼容性: 尽管 OkHttp 4.0+ 优先采用 Kotlin 开发,但其对 Java 开发者依然保持了高度的友好性。

深入理解 OkHttp 的核心运作原理及各关键组件的功能,将有助于开发者在其应用程序中更有效地利用该库,从而构建高效且稳定的网络通信方案。

Licensed under CC BY-NC-SA 4.0
赣ICP备2022001789号
使用 Hugo 构建
主题 StackJimmy 设计