Android开发神器:OkHttp框架源码解析

NetworkInterceptors

CallServiceInterceptor

1.RealInterceptorChain.proceed()

2.EventListener.callStart()也是在RealCall.execute()嵌入到Request调用过程, EventListener.callEnd()位于StreamAllocation中调用

3.Request.Builder

url (String/URL/HttpUrl)

header

CacheControl

Tag (Use this API to attach timing, debugging, or other application data to a request so that you may read it in interceptors, event listeners, or callbacks.)

BridgeInterceptor

Bridges from application code to network code. First it builds a network request from a user request. Then it proceeds to call the network. Finally it builds a user response from the network response.

此拦截器是应用码到网络码的桥接。它会将用户请求封装成一个网络请求并且执行请求,同时它还完成从网络响应到用户响应的转化. 最后Chain.proceed() 方法启动拦截器责任链, RealInterceptorChain中通过递归调用将网络请求以及响应的任务分别分配到各个拦截器中, 然后通过ResponseBuilder.build()方法将网络响应封装, 然后递归调用责任链模式使得调用以及Response处理的过程可以一并写入BridgeInterceptor中

public final class RealInterceptorChain implements Interceptor.Chain { public Response proceed(Request request, StreamAllocation streamAllocation,

HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError();

calls++;

… // Call the next interceptor in the chain.

RealInterceptorChain next = new RealInterceptorChain(interceptors,

streamAllocation, httpCodec,connection, index + 1, request, call,

eventListener, connectTimeout, readTimeout,writeTimeout);

Interceptor interceptor = interceptors.get(index);

Response response = interceptor.intercept(next);

… return response;

}

}

CallServiceInterceptor;

Interceptor的逻辑均在intercept()方法中实现, 在通过Chain实体类获取到请求主题之后,通过BufferedSink接口将请求转发到Okio接口,在拦截过程中通过EventListener接口将拦截器处理状态(主要是RequestBodyStart和RequestBodyEnd两个状态)发送出去

public final class CallServiceInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException {

Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // If there’s a “Expect: 100-continue” header on the request, wait for a "HTTP/1.1 100

// Continue" response before transmitting the request body. If we don’t get that, return

// what we did get (such as a 4xx response) without ever transmitting the request body.

if (“100-continue”.equalsIgnoreCase(request.header(“Expect”))) {

httpCodec.flushRequest();

realChain.eventListener().responseHeadersStart(realChain.call());

responseBuilder = httpCodec.readResponseHeaders(true);

} if (responseBuilder == null) { // Write the request body if the “Expect: 100-continue” expectation was met.

realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength();

CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength));

BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);

bufferedRequestBody.close();

realChain.eventListener()

.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);

} 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();

}

}

}

}

CacheInterceptor;

public final class CacheInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException {

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

Request networkRequest = strategy.networkRequest;

Response cacheResponse = strategy.cacheResponse; if (cache != null) { /**

  • Track an HTTP response being satisfied with {@code cacheStrategy}.

  • 主要是跟踪networkRequest次数以及对应Cache的hitcount

*/

cache.trackResponse(strategy);

} if (cacheCandidate != null && cacheResponse == null) {

closeQuietly(cacheCandidate.body()); // The cache candidate wasn’t applicable. Close it.

} // If we’re forbidden from using the network and the cache is insufficient, fail.

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();

} // If we don’t need the network, we’re done.

if (networkRequest == null) { return cacheResponse.newBuilder()

.cacheResponse(stripBody(cacheResponse))

.build();

} //在chain.proceed()调用下一个拦截器

Response networkResponse = null; try {

networkResponse = chain.proceed(networkRequest);

} finally { // If we’re crashing on I/O or otherwise, don’t leak the cache body.

if (networkResponse == null && cacheCandidate != null) {

closeQuietly(cacheCandidate.body());

}

} //处理response并返回

… return response;

}

}

OkHttpClient

OkHttpClient托管着所有HTTP调用, 每个Client均拥有自己的连接池和线程池

实现抽象类Internal的方法,这是Internel抽象类唯一的实现,方法与CacheInterceptor控制Http的Header.Lenient区域和StreamAlloction从连接池中获取连接有关

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {

… synchronized (connectionPool) {

… if (result == null) { // Attempt to get a connection from the pool.

Internal.instance.get(connectionPool, address, this, null); if (connection != null) {

foundPooledConnection = true;

result = connection;

} else {

selectedRoute = route;

}

}

} return result;

RouteDatabase && RouteSeletor

RouteDatabase是记录连接失败的连接路径的黑名单,从而OkHttp可以从失败中学习并且倾向于选择其他可用的路径,RouteSeletor通过RouteDatabase.shouldPostpone(route)方法可获知此路径是否近期曾连接失败,RouteSelector部分源码如下:

public final class RouteSelector { /**

  • Clients should invoke this method when they encounter a connectivity failure on a connection

  • returned by this route selector.

  • 在StreamAllocation.streamFailed()中添加了routeSelector.connectFailed()逻辑

*/

public void connectFailed(Route failedRoute, IOException failure) { if (failedRoute.proxy().type() != Proxy.Type.DIRECT && address.proxySelector() != null) { // Tell the proxy selector when we fail to connect on a fresh connection.

address.proxySelector().connectFailed(

address.url().uri(), failedRoute.proxy().address(), failure);

}

routeDatabase.failed(failedRoute);

}

}

synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

runningAsyncCalls.add(call);

executorService().execute(call);

} else {

readyAsyncCalls.add(call);

}

}

… /** Used by {@code Call#execute} to signal it is in-flight. */

synchronized void executed(RealCall call) {

runningSyncCalls.add(call);

}

ExecutorSevice.execute(AsyncCall)执行代码位于AsyncCall内部复写的execute()方法, 方法内定义一些Callback回调节点运行逻辑,包括用户主动取消执行(使用retryAndFollowUpInterceptor)以及执行请求成功或者失败时的回调方法

final class AsyncCall extends NamedRunnable {

… @Override protected void execute() { boolean signalledCallback = false; try {

Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) {

signalledCallback = true;

responseCallback.onFailure(RealCall.this, new IOException(“Canceled”));

} else {

signalledCallback = true;

responseCallback.onResponse(RealCall.this, response);

}

} catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice!

Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);

} else {

eventListener.callFailed(RealCall.this, e);

responseCallback.onFailure(RealCall.this, e);

}

} finally {

client.dispatcher().finished(this);

}

}

}

惰性初始模式(Created Lazily)成员

ExecutorService()

CacheControl

WebSocket

WebSocket 异步非堵塞的web socket接口 (通过Enqueue方法来实现)

OkHttpClient 通过实现 WebSocket.Factory.newWebSocket 接口实现工厂构造, 通常是由 OkHttpClient来构造

WebSocket生命周期:

Connecting状态: 每个websocket的初始状态, 此时Message可能位于入队状态但是还没有被Dispatcher处理

Open状态: WebSocket已经被服务器端接受并且Socket位于完全开放状态, 所有Message入队之后会即刻被处理

Closing状态: WebSocket进入优雅的关闭状态,WebSocket继续处理已入队的Message但拒绝新的Message入队

Closed状态: WebSocket已完成收发Message的过程, 进入完全关闭状态

WebSocket受到网络等各种因素影响, 可能会断路而提前进入关闭流程

Canceled状态: 被动WebSocket失败连接为非优雅的过程, 而主动则是优雅短路过程

RealWebSocket

RealWebSocket管理着Request队列内容所占的空间大小以及关闭Socket之后留给优雅关闭的时间,默认为16M和60秒,在RealWeb​
Socket.connect()方法中RealWebSocket对OkHttpClient以及Request封装成Call的形式,然后通过Call.enqueue()方法定义调用成功和失败时的Callback代码

public void connect(OkHttpClient client) {

client = client.newBuilder()

.eventListener(EventListener.NONE)

.protocols(ONLY_HTTP1)

.build(); final Request request = originalRequest.newBuilder()

.header(“Upgrade”, “websocket”)

.header(“Connection”, “Upgrade”)

.header(“Sec-WebSocket-Key”, key)

.header(“Sec-WebSocket-Version”, “13”)

.build();

call = Internal.instance.newWebSocketCall(client, request);

call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { try {

checkResponse(response);

} catch (ProtocolException e) {

failWebSocket(e, response);

closeQuietly(response); return;

} // Promote the HTTP streams into web socket streams.

StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);

streamAllocation.noNewStreams(); // Prevent connection pooling!

Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation); // Process all web socket messages.

try {

listener.onOpen(RealWebSocket.this, response);

String name = "OkHttp WebSocket " + request.url().redact();

initReaderAndWriter(name, streams);

streamAllocation.connection().socket().setSoTimeout(0);

loopReader();

} catch (Exception e) {

failWebSocket(e, null);

}

} @Override public void onFailure(Call call, IOException e) {

failWebSocket(e, null);

}

});

}

当Call请求被服务端响应的时候就将HTTP流导入到Web Socket流中,并且调用WebSocketListener相对应的状态方法, WebSocketListener状态如下:

onOpen()onMessage()onClosing()onClosed()onFailure()

WebSocket -> RealWebSocket

Connection -> RealConnection

Interceptor -> RealInterceptorChain

Call -> RealCall

ResponseBody -> RealResponseBody

Gzip压缩机制

处理Gzip压缩的代码在BridgeInterceptor中,默认情况下为gzip压缩状态,可以从下面的源码片段中获知。如果header中没有Accept-Encoding,默认自动添加 ,且标记变量transparentGzip为true

// If we add an “Accept-Encoding: gzip” header field we’re responsible for also decompressing

// the transfer stream.

boolean transparentGzip = false; if (userRequest.header(“Accept-Encoding”) == null && userRequest.header(“Range”) == null) {

transparentGzip = true;

requestBuilder.header(“Accept-Encoding”, “gzip”);

}

BridgeInterceptor解压缩的过程调用了okio.GzipSource()方法并调用Okio.buffer()缓存解压过程,源码如下

if (transparentGzip

&& “gzip”.equalsIgnoreCase(networkResponse.header(“Content-Encoding”))

&& HttpHeaders.hasBody(networkResponse)) {

GzipSource responseBody = new GzipSource(networkResponse.body().source());

Headers strippedHeaders = networkResponse.headers().newBuilder()

.removeAll(“Content-Encoding”)

.removeAll(“Content-Length”)

.build();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
onse.header(“Content-Encoding”))

&& HttpHeaders.hasBody(networkResponse)) {

GzipSource responseBody = new GzipSource(networkResponse.body().source());

Headers strippedHeaders = networkResponse.headers().newBuilder()

.removeAll(“Content-Encoding”)

.removeAll(“Content-Length”)

.build();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-NvRWbcH6-1719084075754)]一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/858457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux常用

很早以前的 ls: 查看文件夹内所有文件 rz: windows的文件传到linux服务器 sz filename: 将文件下载到windows本地 ctrlinsert:复制 shiftinsert:粘贴 ctrlD&#xff1a;退出spark-shell 运行脚本并输出日志 nohup sh filename.sh > log.log 2>&1 & 查看日…

STM32玩转物联网07-WIFI实验

前言 上一节我们学习了串口的简单使用&#xff0c;本节我们增加难度&#xff0c;做一个demo通过AT指令控制ESP8266&#xff0c;使用DMA方式接收ESP8266发来的数据&#xff0c;后续我们便开始通过ESP8266连接物联网云平台&#xff0c;敬请关注。 一、准备 1. ESP8266硬件准备 准…

在C++中,构造器(Builder)模式的思考(《C++20设计模式》及常规设计模式对比)

文章目录 一、前言二、为什么需要Builder Pattern,Builder Pattern到底解决了什么实际问题&#xff1f;为什么不用set()方法&#xff1f;2.1 初学者有那些对象的属性初始化方法呢&#xff1f;2.1.1 构造函数的弊端2.1.1.1 对于属性的初始化只能是固定的顺序 2.1.2 用set()函数初…

兰州理工大学24计算机考研情况,好多专业都接受调剂,只有计算机专硕不接收调剂,复试线为283分!

兰州理工大学&#xff08;Lanzhou University of Technology&#xff09;&#xff0c;位于甘肃省兰州市&#xff0c;是甘肃省人民政府、教育部、国家国防科技工业局共建高校&#xff0c;甘肃省高水平大学和“一流学科”建设高校&#xff1b;入选国家“中西部高校基础能力建设工…

redis主从复制、哨兵、集群

在实际的生活环境中&#xff0c;如果只使用一个redis进行读写操作&#xff0c;那么面对庞大的访问人群是崩溃的&#xff0c;所以可以有几个redis&#xff0c;一个用来做主机&#xff0c;提供修改数据操作&#xff0c;而这个主机用来控制其他redis&#xff0c;即将更新的发送&am…

Windows程序设计课程作业-3(文件并发下载)

目录 目录 1.作业内容 2.作业要求 3.主要思路 1&#xff09;窗体和组件初始化 2&#xff09;下载管理器实例化 3&#xff09;按钮点击事件处理 4&#xff09;窗体加载事件处理 5&#xff09;下载消息处理 4.主要难点 1&#xff09;多线程管理&#xff1a; 2&#xff09…

材料科学SCI期刊,中科院3区,收稿范围广,易录用

一、期刊名称 International Journal of Material Forming 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;材料科学 影响因子&#xff1a;2.4 中科院分区&#xff1a;3区 三、期刊征稿范围 该杂志发表和传播材料成型领域的原创研究。该研究应构成对材料…

理解广角镜头的视野和畸变

为什么广角镜头的视野会比长焦镜头的视野大呢&#xff1f; 我之前用等光程解释了景深&#xff0c;也解释了为什么焦距越远&#xff0c;成像越大&#xff0c;但是从来没有提到过视野范围这个概念。实际上在我之前建立的数学模型中&#xff0c;物曲面S是无限大的&#xff0c;像曲…

Chromium 调试指南2024 - 远程开发(下)

1. 引言 在《Chromium 调试指南2024 - 远程开发&#xff08;上&#xff09;》中&#xff0c;我们探讨了远程开发的基本概念、优势以及如何选择合适的远程开发模式。掌握了这些基础知识后&#xff0c;接下来我们将深入了解如何在远程环境中高效地进行Chromium项目的调试工作。 …

鹅算法(GOOSE Algorithm,GOOSE)求解复杂城市地形下无人机避障三维航迹规划,可以修改障碍物及起始点(Matlab代码)

一、鹅算法 鹅优化算法&#xff08;GOOSE Algorithm&#xff0c;GOOSE)从鹅的休息和觅食行为获得灵感&#xff0c;当鹅听到任何奇怪的声音或动作时&#xff0c;它们会发出响亮的声音来唤醒群中的个体&#xff0c;并保证它们的安全。 参考文献 [1]Hamad R K, Rashid T A. GOO…

数据结构和算法(1) ---- Queue 的原理和实现

Queue 的定义和结构 队列(Queue) 是只允许在一端进行插入&#xff0c;在另一端进行删除的线性表 队列是一种先进先出(First In First Out)的线性表&#xff0c;简称 FIFO(First IN First OUT), 允许插入的一端称为队尾, 允许删除的一端称为队列头 队列的基本结构如下图所示&a…

FreeCAD中智能指针分析

实现原理 FreeCAD中有两套智能指针&#xff0c;一个是OCC的智能指针handle&#xff0c;另一个是自己定义的智能指针Reference&#xff0c;两种智能指针都是通过引用计数方式管理指针。 1.1 OCC智能指针handle OCC在基础类包中定义了一个模板类handle&#xff0c;该类包含一个私…

大学物理(下)笔记

摘录来自笔记网站的笔记。笔记网站详见https://onford.github.io/Notes/。 大学物理&#xff08;下&#xff09;笔记 部分常用物理常量的计算值 C h a p t e r 9 Chapter9 Chapter9 恒定磁场 毕奥-萨伐尔定律 磁场和电场在很多性质上是有共性的&#xff0c;很多时候可以拿它…

【pytorch05】索引与切片

索引 a[0,0]第0张图片的第0个通道 a[0,0,2,4]第0张图片&#xff0c;第0个通道&#xff0c;第2行&#xff0c;第4列的像素点&#xff0c;dimension为0的标量 选择前/后N张图片 a[:2,:1,:,:].shape前两张图片&#xff0c;第1个通道上的所有图片的数据 a[:2,1:,:,:].shape前两张…

ADD属性驱动架构设计(一)

目录 一、架构设计过程 1.1、架构设计过程 1.1.1、设计目的 1.1.2、质量属性&#xff08;非功能需求&#xff09; 1.1.3、核心功能&#xff08;功能需求&#xff09; 1.1.4、架构关注 1.1.5、约束条件 1.2、基于设计过程 二、什么是ADD? 三、为什么选择ADD? 四、作…

本地离线模型搭建指南-中文大语言模型底座选择依据

搭建一个本地中文大语言模型&#xff08;LLM&#xff09;涉及多个关键步骤&#xff0c;从选择模型底座&#xff0c;到运行机器和框架&#xff0c;再到具体的架构实现和训练方式。以下是一个详细的指南&#xff0c;帮助你从零开始构建和运行一个中文大语言模型。 本地离线模型搭…

鸿蒙开发系统基础能力:【@ohos.hiAppEvent (应用打点)】

应用打点 本模块提供了应用事件打点能力&#xff0c;包括对打点数据的落盘&#xff0c;以及对打点功能的管理配置。 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import hiAppEve…

今日分享:中国石油年金系统交互、视觉设计和vue开发

金融系统交互及UI设计时&#xff0c;需注意简洁明了、色彩合理、字体统一、交互易用、安全感和用户控制。确保用户快速理解、安全操作并提升体验。

数据结构~~时间、空间复杂度

目录 一、什么是数据结构 什么是算法 算法的复杂度 二、时间复杂度 三、空间复杂度 四、总结 一、什么是数据结构 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的 数据元素的集合。 数据结构关注的是数据的逻辑结…

快速搭建Jenkins自动化集成cicd工具

一、简介 jenkins是一款优秀的自动化持续集成运维工具&#xff0c;可以极大的简化运维部署的步骤。 传统的项目部署需要手动更换最新的项目代码&#xff0c;然后打包并运行到服务器上。 使用Jenkins可以自动化实现&#xff0c;当代码编写完成并提交到git后&#xff0c;Jenki…