OkHttp3 中几个拦截器基本功能介绍

RetryAndFollowUpInterceptor

功能:实现重试、跟踪

实现原理:

while(true) 死循环的实现。

检验返回的 Response ,如果没有异常(包括请求失败、重定向等),那么执行 return Response, return 会直接结束循环操作,将结果返回到下一个拦截器中进行处理。

检验返回的 Response ,如果出现异常情况,那么会根据 Response 新建 Request,并且执行一些必要的检查(是否为同一个 connnetion ,是的话抛出异常,不是的话是否旧的 connection 的资源,并新建一个 connection),进入死循环的下一次循环,那么此时将进行新一轮的拦截器的处理。

BridgeInterceptor

功能
  • 将用户构建的 Request 请求转换为能够进行网络访问的请求。

在用户构建的 Request 的基础上 添加了许多的请求头,具体内容参看代码。

  • 将符合网络请求的 Request 进行网络请求。

在责任链模式的过程中,在此拦截器的到响应 Response。

Response networkResponse = chain.proceed(requestBuilder.build());
  • 将请求回来的响应 Response 转化为用户可用的 Response。

主要是根据响应是否对 Response 进行 gzip 压缩,具体是使用 Okio 的库对 Response 进行压缩,并返回 Response。

CacheInterceptor

功能: 实现缓存功能的拦截器

设置启用缓存功能

在新建 OkhttpClient.Builder 的时候进行设置:

File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
    .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
mOkHttpClient = builder.build();

其底层实现还是 大神 的 开源库 DiskLruCache,如下可以看到:

Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
缓存策略的基本流程
1. 获取缓存响应
Response cacheCandidate = cache != null
                ? cache.get(chain.request())
                : null;//本地缓存
2. 根据 request缓存响应 cacheCandidate 获取缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
3. 获取响应缓存策略下的 request 和 response
//缓存策略中的请求
Request networkRequest = strategy.networkRequest;
//缓存策略中的响应
Response cacheResponse = strategy.cacheResponse;
4. 根据响应缓存策略下的 request 和 response 分情况判断几种具体情况。
1. 缓存响应不为空但是策略的响应为空,关闭缓存响应流
if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
2. networkRequest 和 cacheResponse 皆为空,构建 504 的响应,直接返回
if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(504)
            .message("Unsatisfiable Request (only-if-cached)")
            .body(Util.EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    }
3. networkRequest 为空,直接使用缓存,返回缓存响应
if (networkRequest == null) {
    return cacheResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build();
}
4. 获取网络请求的响应后,进行操作,此时也要分情况讨论。
Response networkResponse = null;
networkResponse = chain.proceed(networkRequest);
  1. networkResponse 的响应码为 304,说明请求的资源未过期,构建 Response 对象,直接反正该对象
if (cacheResponse != null) {
    // 304 304 的标准解释是:Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(
    // 一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。
    // 服务器告诉客户,原来缓冲的文档还可以继续使用。
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    networkResponse.body().close();

     // Update the cache after combining headers but before stripping the
    // Content-Encoding header (as performed by initContentStream()).
    cache.trackConditionalCacheHit();
    cache.update(cacheResponse, response);
    return response;
    } else {
        closeQuietly(cacheResponse.body());
    }
}
  1. 根据 构建 Response,并直接返回该对象
Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();
    
....

return response;
5. 将 Response 写入缓存
if (cache != null) {
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
    // Offer this request to the cache.
    CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
    }

    if (HttpMethod.invalidatesCache(networkRequest.method())) {
    try {
        cache.remove(networkRequest);
        } catch (IOException ignored) {
        // The cache cannot be written.
        }
    }
}

以上被标注为 黄色 的字样,说明执行 return Response 操作,直接返回响应,进入下一个拦截器的相关处理。

ConnectInterceptor

ConnectInterceptor

功能: Opens a connection to the target server and proceeds to the next interceptor。

打开一个面向指定服务器的连接,并且执行下一个拦截器。

HttpCodec

在这个拦截器中 HttpCodec 的作用是编码 Http 请求和解码 Http 响应。根据 HTTP版本不同分为

  • Http1Codec(HTTP/1.1)
  • Http2Codec(HTTP/2)

打开连接的关键代码为:

HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);

以下为具体代码调用链:

StreamAllocation#newStream() 
--> this#findHealthyConnection(..) 
-->this#findHealthyConnection(..)//获得连接的顺序:存在的链接 、 连接池、新建一个连接
-->this#findConnection(...)
-->RealConnection#connect(...)// 连接并握手
-->RealConnection#connectTunnel(...)或
   RealConnection#connectSocket(..)(最终都会调用connectSocket(...))
-->Platform.get()#connectSocket(...)
-->socket.connect(address, connectTimeout);//最终可以获得建立连接后的 Socket
-->RealConnection#newCodec(..)// 返回 HttpCode

在 findHealthyConnection() 中有以下代码进行连接:

result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);

至此连接指定服务器的 connection 已经建立。

CallServerInterceptor

这是 Okhttp 库中拦截器链的最后一个拦截器,也是这个拦截器区具体发起请求和获取响应。

大致分为以下几个步骤:

  1. 写入请求头
httpCodec.writeRequestHeaders(request);
  1. 根据具体情况判断是否读取

  2. 根据具体情况判断是否写入相应请求头

if (responseBuilder == null) {
                // Write the request body if the "Expect: 100-continue" expectation was met.
                Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
                BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
                request.body().writeTo(bufferedRequestBody);
                bufferedRequestBody.close();
            } else if (!connection.isMultiplexed()) {
                // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
                // being reused. Otherwise we're still obligated to transmit the request body to leave the
                // connection in a consistent state.
                streamAllocation.noNewStreams();
            }
  1. 构建 Response
 Response response = responseBuilder
                .request(request)
                .handshake(streamAllocation.connection().handshake())
                .sentRequestAtMillis(sentRequestMillis)
                .receivedResponseAtMillis(System.currentTimeMillis())
                .build();

  1. 写入 Response 的 body
if (forWebSocket && code == 101) {
            // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
            response = response.newBuilder()
                    .body(Util.EMPTY_RESPONSE)
                    .build();
        } else {
            response = response.newBuilder()
                    .body(httpCodec.openResponseBody(response))
                    .build();
        }

至此,网络请求经过拦截器链获得 Response ,那么再按照拦截器链逆向返回 Response,在此过程中对 Response 进行相应的处理。

总结

在对开源库的研读中,我们首先需要做的是对大致流程有个清晰的认识,但是不能深陷细节、具体实现上在后期对相关功能的具体使用时在进行相关研究。而自己在此过程中,就深陷入细节,针对具体的实现真是绞尽脑汁,最后还是 “一败涂地”。此处再次告诫自己和后来人:对开源库的研读不要纠结于细节,不要纠结于细节。

参考资源链接:[Android平台Brotli压缩实现及Okhttp拦截器应用](https://wenku.csdn.net/doc/30ump4dq5o?utm_source=wenku_answer2doc_content) 在追求网络请求性能和效率的Android应用开发,合理集成Brotli压缩算法是一个有效的优化策略。首先,你需要确保你的应用支持Brotli算法。根据知识点二,从Android 5.0 Lollipop(API级别21)开始,Android平台原生支持了Brotli算法。这意味着开发者可以直接使用Java标准库的java.util.zip.BrotliOutputStream和java.util.zip.BrotliInputStream来处理Brotli压缩和解压缩。 然而,为了更简便地在Okhttp网络请求实现Brotli压缩,你可以参考《Android平台Brotli压缩实现及Okhttp拦截器应用》这本书籍。书详细介绍了如何将Brotli压缩逻辑封装成Okhttp拦截器,这样开发者就可以轻松地将压缩功能集成到Okhttp客户端。这一封装过程涉及以下几个关键步骤: 1. 创建Brotli压缩拦截器类,实现Interceptor接口。在这个类,你需要检查HTTP请求是否需要压缩。这通常通过请求头的'Accept-Encoding'字段来判断,如果该字段包含'brotli',则说明客户端支持Brotli压缩。 2. 在拦截器的intercept方法,根据需要对请求或响应进行Brotli压缩或解压缩。如果请求需要压缩,则在发送请求前对请求体进行Brotli压缩;如果响应需要压缩,则在接收响应后对其内容进行Brotli解压缩。 3. 将Brotli拦截器添加到Okhttp客户端实例。这样,每当客户端发起请求或处理响应时,Brotli拦截器会自动进行压缩或解压缩操作。 4. 在项目添加Okhttp和Brotli库的依赖。你需要在项目的build.gradle文件添加对应的库依赖,确保应用能够正确地构建和运行。 通过以上步骤,你的Android应用将能够在不改变现有网络请求逻辑的情况下,提升网络传输效率,加速内容加载,从而改善用户体验。由于Brotli算法的高效压缩比和快速的压缩/解压缩速度,它对于减少数据传输量、降低带宽消耗以及加快页面加载速度非常有效。 在完成Brotli压缩功能的集成后,为了进一步提升Android应用的性能,可以参考《Android平台Brotli压缩实现及Okhttp拦截器应用》的高级应用和最佳实践,以及更多关于网络优化和性能提升的知识。 参考资源链接:[Android平台Brotli压缩实现及Okhttp拦截器应用](https://wenku.csdn.net/doc/30ump4dq5o?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值