Okhttp实现原理

OkHttp 是一个高效的 HTTP 客户端库,广泛应用于 Android 和 Java 应用中。它提供了简洁的 API,支持多种协议,如 HTTP/1.x 和 HTTP/2,并且内置了缓存和重试机制。下面是结合源码分析的 OkHttp 的实现原理:

核心组件

OkHttp 的核心组件包括:

  • Call :这是 OkHttp 的主要接口,用于发起 HTTP 请求。一个 Call 对象代表一个 HTTP 请求。
  • Request:包含请求的所有信息,如 URL、HTTP 方法、请求头和请求体。
  • Response:包含服务器响应的所有信息,如状态码、响应头和响应体。
  • Dispatcher :管理 OkHttp 的并发请求和线程池。
  • ConnectionPool:管理 HTTP 连接的复用和释放。
  • Interceptor:提供拦截器链,允许在请求发送前和响应接收后进行修改。

请求的发起和执行

当你调用 OkHttpClientnewCall() 方法创建一个 Call 对象,并调用 execute()enqueue() 方法时,请求的执行流程如下:

  1. 创建 Call 对象Call 对象被创建,它持有 Request 对象的引用,并通过 execute() 同步执行请求或通过 enqueue() 异步执行请求。

  2. 分发请求Call 对象会调用 Dispatcher 来管理请求的执行。如果使用 enqueue()Dispatcher 会在后台线程中执行请求。

  3. 拦截器链 :请求经过一系列拦截器的处理。OkHttp 的拦截器按顺序执行,每个拦截器都可以修改请求或响应。拦截器链的最后一个拦截器是 RealInterceptorChain,它负责实际的网络请求。

  4. 建立连接 :如果需要,OkHttp 会建立到服务器的 TCP 连接。它会优先使用连接池中的现有连接,如果没有合适的连接,则创建新的连接。

  5. 发送请求:将请求写入到套接字中,等待服务器响应。

  6. 读取响应:读取服务器的响应,解析状态码、响应头和响应体。

  7. 关闭连接:如果不需要保留连接,则关闭连接。否则,连接会被放回连接池。

  8. 处理响应:响应被传递给请求的回调方法或同步返回。

源码分析

Call 的执行流程为例,看看 OkHttp 的执行流程:

复制代码
Java
1// OkHttpClient.java
2public Response execute(Request request) throws IOException {
3    synchronized (this) {
4        if (closed) throw new IllegalStateException("closed");
5    }
6
7    // Create a new call.
8    Call call = new RealCall(this, request);
9    return call.execute();
10}
11
12// RealCall.java
13public Response execute() throws IOException {
14    // ...
15    RealConnection connection = getConnection();
16    // ...
17
18    // Execute the request.
19    long startNs = System.nanoTime();
20    try {
21        StreamAllocation streamAllocation = connection.newStream(false /* doNotRetry */, false /* isMultiplexed */);
22        // ...
23        return readResponse(streamAllocation, responseBuilder);
24    } finally {
25        streamAllocation.finishedReadingResponseBody();
26        // ...
27    }
28}

在这段代码中,OkHttpClientexecute() 方法创建了一个 RealCall 实例,然后调用 RealCallexecute() 方法来执行请求。RealCallexecute() 方法会获取或创建一个 RealConnection,然后通过这个连接发送请求,并读取响应。

拦截器(Interceptors)

拦截器是 OkHttp 中非常重要的概念,它允许你在请求发送前和响应到达后添加自定义的逻辑。OkHttp 使用了一个拦截器链来组织多个拦截器的执行顺序,这样就可以在请求和响应的不同阶段添加额外的行为。

Interceptor Chain

RealCall 的执行过程中,拦截器链通过 RealInterceptorChain 类实现。每一个 Interceptor 都可以访问到请求和响应,并有机会修改它们。拦截器链中的最后一个拦截器是 NetworkInterceptor,它负责实际的网络请求。

Java

1// RealInterceptorChain.java
2public Response proceed(Request request) throws IOException {
3    if (index == interceptors.size()) throw new AssertionError("no network interceptor");
4
5    // Call the next interceptor in the chain.
6    Interceptor interceptor = interceptors.get(index);
7    index++;
8
9    if (interceptor == networkInterceptor) {
10        // This is the last interceptor. Time to make the network call!
11        RealConnection connection = connect();
12        return withConnection(connection, new Callable<Response>() {
13            @Override public Response call() throws IOException {
14                return networkInterceptor.intercept(networkChain.proceed(request));
15            }
16        });
17    } else {
18        // Not the last interceptor. Proceed down the chain.
19        return interceptor.intercept(this);
20    }
21}

连接池(Connection Pool)

OkHttp 使用连接池来复用 HTTP 连接,这对于提高应用性能至关重要,尤其是对于需要频繁发起网络请求的场景。连接池中的连接可以被重复使用,从而避免了频繁创建和销毁连接所带来的开销。

ConnectionPool

ConnectionPool 是 OkHttp 中管理连接复用的核心组件。它会缓存空闲的连接,并在需要时提供给请求使用。当连接不再需要时,它们会被放回连接池,直到达到最大限制或连接过期。

复制代码
Java
1// ConnectionPool.java
2public synchronized void put(Connection connection) {
3    if (connection == null) throw new NullPointerException("connection == null");
4    if (!connection.isEligibleForPool()) return;
5
6    String routeKey = connection.route().hostAddress();
7    Evictor<Connection> evictor = evictors.get(routeKey);
8    if (evictor == null) {
9        evictor = new Evictor<>(this, routeKey);
10        evictors.put(routeKey, evictor);
11    }
12    evictor.put(connection);
13}

重试机制

OkHttp 支持自动重试,可以在网络不稳定或服务器暂时不可用的情况下自动重发请求。重试机制是通过拦截器实现的,可以配置重试次数和重试条件。

RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 是 OkHttp 内置的一个拦截器,用于处理重试和重定向。它会根据网络状况和服务器响应的状态码决定是否重试请求。

Java

1// RetryAndFollowUpInterceptor.java
2public Response intercept(Chain chain) throws IOException {
3    Request request = chain.request();
4    int attemptNumber = 1;
5
6    while (true) {
7        Response response = chain.proceed(request);
8
9        if (attemptNumber > retryCount) break;
10
11        if (response.isSuccessful()) break;
12
13        // Decide whether to retry based on the response code and other conditions.
14        if (shouldRetry(response, attemptNumber)) {
15            // Close the socket before retrying.
16            response.close();
17            request = getNextRequest(response, request);
18            attemptNumber++;
19        } else {
20            break;
21        }
22    }
23
24    return response;
25}

缓存

OkHttp 还支持响应缓存,可以将服务器响应存储在本地,以减少未来的网络请求。缓存的实现涉及多个组件,包括 Cache 类和相关的拦截器。

Cache

Cache 类用于管理缓存文件和元数据。它遵循 HTTP 协议中的缓存控制规则,以决定哪些响应可以被缓存,以及何时重新验证缓存的有效性。

复制代码
Java
1// Cache.java
2public Response get(Request request) throws IOException {
3    // Check if we have a cached response that can be used.
4    // If so, return it.
5    // Otherwise, return null.
6}
7
8public void update(Response networkResponse, Response cacheResponse) throws IOException {
9    // Update the cache with the latest network response.
10}

TLS 和 SSL/TLS 协商

OkHttp 支持 HTTPS 协议,即安全的 HTTP 协议,这要求客户端与服务器之间进行 SSL/TLS 握手。OkHttp 使用 Java 的 SSLSocketFactorySSLContext 来处理 TLS 连接的建立。

SSLSocketFactory 和 SSLSession

在 OkHttp 中,OkHttpClient.Builder 提供了 sslSocketFactory(SSLSocketFactory, X509TrustManager) 方法,允许开发者自定义 SSLSocketFactory 和信任管理器。这使得 OkHttp 能够处理自签名证书、过期证书或任何其他不被标准信任库接受的证书。

复制代码
Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder sslSocketFactory(SSLSocketFactory sslSocketFactory,
3                                            X509TrustManager trustManager) {
4    if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null");
5    if (trustManager == null) throw new NullPointerException("trustManager == null");
6    this.sslSocketFactory = sslSocketFactory;
7    this.trustManager = trustManager;
8    return this;
9}

DNS 解析

OkHttp 支持多种 DNS 解析策略,包括标准的 DNS 解析、DNSSEC、IPv6 和多播 DNS。通过 Dns 接口,OkHttp 允许开发者自定义 DNS 解析行为。

Dns 接口

Dns 接口定义了 lookup 方法,该方法返回一个主机名的 IP 地址列表。OkHttp 默认使用系统的 DNS 解析器,但可以通过 OkHttpClient.Builderdns(Dns) 方法来替换。

复制代码
Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder dns(Dns dns) {
3    if (dns == null) throw new NullPointerException("dns == null");
4    this.dns = dns;
5    return this;
6}

超时

OkHttp 提供了全面的超时控制,包括连接超时、读取超时和写入超时。这些超时策略有助于处理网络延迟和服务器响应缓慢的问题。

超时配置

通过 OkHttpClient.Builder,可以设置全局的超时策略,也可以在每个请求中单独指定超时时间。

复制代码
Java
1// OkHttpClient.Builder.java
2public OkHttpClient.Builder connectTimeout(int duration, TimeUnit unit) {
3    if (duration < 0) throw new IllegalArgumentException("duration < 0");
4    if (unit == null) throw new NullPointerException("unit == null");
5    this.connectTimeoutMillis = unit.toMillis(duration);
6    return this;
7}
8
9public OkHttpClient.Builder readTimeout(int duration, TimeUnit unit) {
10    if (duration < 0) throw new IllegalArgumentException("duration < 0");
11    if (unit == null) throw new NullPointerException("unit == null");
12    this.readTimeoutMillis = unit.toMillis(duration);
13    return this;
14}
15
16public OkHttpClient.Builder writeTimeout(int duration, TimeUnit unit) {
17    if (duration < 0) throw new IllegalArgumentException("duration < 0");
18    if (unit == null) throw new NullPointerException("unit == null");
19    this.writeTimeoutMillis = unit.toMillis(duration);
20    return this;
21}

HTTP/2 支持

OkHttp 自版本 3.0 开始支持 HTTP/2 协议,这是一种二进制、多路复用的 HTTP 协议,相比 HTTP/1.1 能够显著减少延迟和提高性能。

HTTP/2 连接

OkHttp 使用 Okio 库来实现高效的 I/O 操作,包括 HTTP/2 的帧处理和流控制。在 HTTP/2 连接中,多个请求可以在同一个 TCP 连接上并行处理,从而避免了 HTTP/1.1 中的队头阻塞问题。

复制代码
Java
1// RealConnection.java
2public synchronized StreamAllocation newStream(boolean doNotRetry, boolean isMultiplexed) throws IOException {
3    // ...
4    if (isMultiplexed && !supportsMultiplexing()) throw new ProtocolException("Multiplexing not supported");
5
6    // ...
7    return new StreamAllocation(this, doNotRetry, isMultiplexed);
8}

性能优化

OkHttp 通过多种技术来优化网络请求的性能,包括:

  • 连接池:通过复用已有的 TCP 连接,避免了每次请求都要建立新连接的开销。
  • HTTP 缓存:通过遵循 HTTP 缓存控制头,减少不必要的网络往返。
  • 压缩:支持 HTTP 压缩,减少传输的数据量。
  • DNS 缓存:缓存 DNS 解析结果,减少 DNS 查询的时间。

流式上传和下载

OkHttp 支持流式上传和下载,这意味着在上传或下载大数据时,数据可以分块传输,而不是一次性加载到内存中。这对于处理大文件或长时间运行的请求尤其有用。

流式上传

在流式上传中,你可以使用 RequestBody 的子类 BufferedSink 来逐步写入数据,而不是一次性创建一个完整的 RequestBody 对象。

复制代码
Java
1// 创建一个流式上传的 RequestBody
2RequestBody requestBody = new RequestBody() {
3    @Override
4    public MediaType contentType() {
5        return MediaType.parse("application/octet-stream");
6    }
7
8    @Override
9    public void writeTo(BufferedSink sink) throws IOException {
10        // 逐块写入数据
11        byte[] buffer = new byte[1024];
12        FileInputStream fis = new FileInputStream("largefile.dat");
13        int length;
14        while ((length = fis.read(buffer)) != -1) {
15            sink.write(buffer, 0, length);
16        }
17        fis.close();
18    }
19};
流式下载

流式下载允许你逐块读取响应体,而无需一次性加载整个响应到内存中。

复制代码
Java
1// 创建一个流式下载的请求
2Request request = new Request.Builder()
3    .url("https://example.com/largefile")
4    .build();
5
6// 执行请求并逐块读取响应
7Response response = client.newCall(request).execute();
8ResponseBody body = response.body();
9if (body != null) {
10    BufferedSource source = body.source();
11    byte[] buffer = new byte[1024];
12    FileOutputStream fos = new FileOutputStream("output.dat");
13    long totalBytesRead = 0L;
14    int read;
15    while ((read = source.read(buffer)) != -1) {
16        totalBytesRead += read;
17        fos.write(buffer, 0, read);
18    }
19    fos.close();
20}

身份验证

OkHttp 支持多种身份验证机制,包括基本认证、摘要认证、OAuth 等。

基本认证
复制代码
Java
1// 使用基本认证的请求
2String credentials = "username:password";
3String encodedCredentials = Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
4Request request = new Request.Builder()
5    .url("https://example.com/api")
6    .header("Authorization", "Basic " + encodedCredentials)
7    .build();

跨域资源共享(CORS)

在处理跨域请求时,OkHttp 可以帮助你处理预检请求(OPTIONS 请求),这是 CORS 的一部分,用于确定是否允许跨域请求。

处理预检请求

在服务器端,你需要确保响应 OPTIONS 请求,并设置适当的 CORS 头部,如 Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers。在客户端,OkHttp 会自动处理这些预检请求,你只需要正常发起你的主请求即可。

日志记录

OkHttp 提供了一个内置的日志拦截器,可以帮助你调试网络请求和响应。

复制代码
Java
1// 添加日志拦截器
2HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
3loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
4client = new OkHttpClient.Builder()
5    .addInterceptor(loggingInterceptor)
6    .build();

性能监控

为了确保 OkHttp 的性能,你可能需要监控网络请求的指标,如响应时间、请求成功率和失败原因。OkHttp 提供了多种方式来收集这些数据。

使用 Listeners

OkHttp 的 EventListener 允许你监听网络请求的生命周期事件,如请求开始、响应接收、连接建立等。这可以帮助你收集性能数据和调试网络问题。

Java

1// 创建一个 EventListener 来监听请求事件
2class MyEventListener extends EventListener {
3    @Override
4    public void callStart(Call call) {
5        // 请求开始
6    }
7
8    @Override
9    public void responseReceived(Call call, Response response) {
10        // 响应接收
11    }
12
13    // 更多事件...
14}
15
16// 添加 EventListener 到 OkHttpClient
17OkHttpClient client = new OkHttpClient.Builder()
18    .eventListenerFactory(() -> new MyEventListener())
19    .build();

最佳实践

在使用 OkHttp 时,有一些最佳实践可以帮助你构建更健壮、更高效的网络层:

  • 使用连接池:始终使用连接池来复用连接,避免频繁的连接建立和关闭。
  • 处理异常:确保你的代码能够优雅地处理网络异常,如超时、中断和服务器错误。
  • 缓存策略:合理使用缓存,遵循 HTTP 缓存控制头,减少不必要的网络往返。
  • 安全:始终使用 HTTPS,确保数据传输的安全。
  • 资源回收 :确保在使用完 ResponseBody 或其他资源后调用 close() 方法,以避免内存泄漏。

通过深入理解 OkHttp 的这些特性和最佳实践,你可以构建出既高效又可靠的网络请求层,为你的应用程序提供坚实的基础。

相关推荐
戏谑3 分钟前
Android 常用布局
android·view
拭心12 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王14 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡14 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道15 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库15 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道16 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe16 小时前
Android Hook - 动态加载so库
android
居居飒17 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He20 小时前
桌面列表小部件不能点击的问题分析
android