OkHttp完全解读

一,概述

OkHttp作为android非常流行的网络框架,笔者认为有必要剖析此框架实现原理,抽取并理解此框架优秀的设计模式。OkHttp有几个重要的作用,如桥接、缓存、连接复用等,本文笔者将从使用出发,解读源码,剖析此功能的实现原理。最后阅读完源码后总结出如下结论,OkHttp是一款优秀的网络请求框架,内部采用优雅的责任链模式、构造模式、桥接模式、享元模式、门面模式等设计模式,符合依赖导致原则、里氏替换原则等面向对象原则,将复杂的网络请求封装从简单的调用,属实优雅。笔者推荐感兴趣的读者从笔者粗陋的源码解读思路去思考更多的源码设计与实现,彻底完全了解OkHttp的设计思路,并抽象出时序图和类图。

以下是笔者粗陋的时序图总结,供读者参考。

二,主要成员解读

1,OkHpptClient

如上图,我们从OkHttp提供的API出发。

OkHttpClient,从名字可知,网络请求的客户端,该客户端主要的作用是什么呢?不妨通过其Builder的重要成员进行分析,

dispatch分发器,主要负责网络请求队列的调度,稍后再谈。

connectionPool,连接池。

interceptors,拦截器-责任链,OkHttp内置了几个重要的拦截器,有失败-重定向、桥接、缓存、连接、访问服务等。

剩下的成员读者可自行分析。那么OkHttpCient笔者认为是一系列网络请求的基础配置。

2,Call

Call是一个接口,继承了克隆接口,我们看下定义的方法,

request,返回Request,

execute,同步执行此处请求,

enqueue,异步执行此次请求,

cancel,取消此次请求,

isExecuted,是否执行完毕,

isCanceld,是否成功取消,

timeout,返回超时相关

因此,Call可以理解为一次Request,通过OkHttpClient.newCall创建,其唯一实现是RealCall,

构造函数中需传入OkHttpClient,原始请求Request,是否WebSocket属性。

3,Request

Request不用笔者多说,主要封装request,包裹了url、method、headers、body等http基础request字段。

4,Response

封装了message,code,hearer,body等。

以上Request和Response是网络请求的主要实体类。

5,Dispatch

笔者认为这是异步请求时主要的调度器,调度对象是Call,内部定义有如下三种队列,

raedyAsyncCalls是通过Call#enqueue添加到此的Call队列,

runningAsyncCalls,从readyAsyncCalls中转移到此队列,表现正在运行的异步Call,

runningSyncCalls,同步运行队列,

下面我们看下调度方法promoteAndExecute

主要在enqueue、finished时调用,作用是将符合条件的raedyAsyncCalls转移至runningAsyncCalls中。

在从readyAsyncCalls转移到runningAsyncCalls时,有默认最大运行数maxRequests64。将真正执行运行的Call放进executeableCalls,然后碟调调用其executeOn方法,传入的executorService是一个线程池,

核心线程数定义0,最大容量MAX_VALUE,因maxRequests的存在,可以忽略此参数。

6,Interceptor&Chin

Interceptor只定义了一个方法Intercept,参数为Chain,每个拦截器负责处理自己的逻辑,如果处理完毕并且需要下一个拦截器处理,需要显式调用Chain#proceed方法。

我们看下Chain接口的唯一实现RealInterceptorChain,

传入此次请求call,拦截器,拦截器执行下标index0,原始请求request等,主要供多个拦截器从Chain中获取信息,我们看下核心方法process,

Interceptor每调用一次proceed方法,会触发Chain被负责,传入index+1(这使得同一个拦截器可以多次调用proceed方法,从该节点重试),进一步获取到下一个拦截器,再调用其拦截器intercept方法。这样就实现了链式请求。

以上,我们介绍了OkHttp框架的主要角色,下面介绍下一次请求的主要流程,以及各个重要拦截器所做的工作。

三,一次请求解读

1,伊始

笔者还得从创建了一个Call对象开始,

我们跟进enqueue方法

原子式设置executed为true,否则抛出异常,合理,一个call只能调用enqueue一次。

client是通过RealCall构造方法传入,我们进入Dispatcher#enqueue查看。笔者注意,这里的RealCall在进入dispatcher时,被转换成了AsyncCall,这个稍后再谈。

所做的事情,是将异步call放入readAsyncCalls中,如果不是webSocket需做点什么,这个笔者不展开说,主要调用到调度方法promoteAndExecute。

这个方法如在Dispatch中解读,加入runningAsyncCalls,并且加入到executableCalls列表,调用AsyncCall#executeOn方法,我们跟进。

AsyncCall实现了Running接口,并且封装了RealCall,我们直接看run方法的实现,

通过TimeOut#enter开启请求超时调度(如果设置的话),然后最重要的调用了getResponseWithInterceptorChain方法,直接返回请求到的Response。当请求完毕后,最终调用到dispatcher.finished方法,我们暂且先查看finished方法,

将执行完毕的Call从runingAsyncCalls中移除,然后在调用一次promoteAndExecute方法,将准备队列的Call执行,如果没有Call执行了,就调用闲置回调idleCallback。这样就实现了队列的简单调度。那么,我们将注意力重新回到核心方法getResponseWithInterceptorChain。

创建一个拦截器list,先放入OkHttpClient中用户自定义的拦截器,随后放入几个核心拦截器,

RetryAndFollowUpInterceptor、负责重试重定向的拦截器。

BridgeInterceptor、桥接拦截器,负责自动设置一些heads、cook等。

CacheInterceptor、缓存的核心实现拦截器。

ConnectInterceptor、连接拦截器,维护了一个连接池,复用连接核心逻辑拦截器。

CallServerInterceptor、与服务器正式请求的拦截器,

这些全部封装进RealInterceptorChain方法中,然后调用proceed方法,参数是request,

通过上文我们了解proceed是顺序调用下一个拦截器逻辑,因此,笔者这里暂忽略用户自定义拦截器,直接顺序解读核心拦截器实现逻辑。

2,RetryAndFollowUpInterceptor

一个无限循环,直接调用chain.process让下一个拦截器处理,然后解析Response,

通过followUpRequest解析Response,如果返回空,代表无需重试或重定向,直接返回Response。否则,重复调用chain#proceed(注意,chain在realChanin中通过copy方法实现原型模式,因此后面的index+1对此处无影响,chain#index仍为原始值)

我们看下followUpRequest方法,

对各种Response#code作解析,新创建Request,当Response正常返回,此方法返回null,笔者在此处不展开说,有兴趣的读者可自行研究。接下来,我们看下一个拦截器。

3,BridgeInterceptor

此处,将OkHttpClient的cookieJar保存,继续跟进拦截逻辑,

(1)如果存在body,body中存在contentType,自动设置进Request的Hearer中,

(2)如果存在body,且内容长度不等于-1,自动设置Content-Length头,移除Transfer-Encoding头。否则,移除Content-Length头,添加Transfer-Encoding头。感兴趣的读者可以主动去了解下这些请求头的意思。

(3)如果Request的Hearer中Host为空,则从请求url中设置host。

(4)如果没有设置Connection字段,自动设置“Keep-Alive”,意保持连接。

(5)继续添加请求头,Cookie、User-Aagent,

笔者在这里解释,桥接拦截器的作用就是自动设置一些请求头,减少客户端操作复杂度。

接下来,就是对Response作解析,如cookieJar解析、Response#解码相关,感兴趣的读者自行了解,笔者在此不展开讲。

4,CacheInterceptor

实现缓存相关,核心逻辑如下,

根据Request从cache中获取Response,随后获取Request的缓存策略

如果从缓存中获取到Response,但是cacheResponse为null,代表此次请求不适用缓存,调用closeQuitely关闭缓存。

如果不能使用网络,且无缓存,返回失败Response。

如果不访问网络请求,那就直接从缓存中获取并返回,就不走接下来的拦截器了。

如果可以访问网络,但策略是访问网络,调用listenr#cacheConditionlHit回调,通知观察者缓存命中或缓存没命中。

于是,请求到下一个拦截器,当返回Response时,

Response code 返货 not modified,调用cache方法更新缓存

如果Response有效,添加到缓存中,另外笔者注意到,如果method不支持缓存,则移除,我们看下哪些不支持呢?

POST/DELETE/PATCH/PUT/MOVE是不支持缓存的,而GET/HEAD...才支持缓存。

5,ConnectInterceptor

通过获取到exchange,调用realChain#copy方法将exchange传入,作为一次连接复用。我们跟进看看initExchange方法,

通过exchangeFinder#find方法复用ExchangeCodec,笔者猜测这是实现连接的主要核心类,我们现看下ExchangeCodec是什么。ExchangeCodec是一个接口,方法定义如下,

从方法中,笔者猜测到这代表了连接实体,通过flushRequest发送给服务器请求,我们看看这个接口的实现类

笔者这里看下Http1ExchangeCodec,有兴趣的读者可以自己去看Http2ExchangeCodec,

从成员中发现RealConnection,因此这封装了一次连接;

BufferedSink,从命名知这是连接打开的缓冲区,通过flushReques将缓存区的数据发送给服务端。具体如何发送给服务器笔者暂不跟进。

先回到复用逻辑,exchangeFinder#find方法,

(1),首先从连接池中获取连接,如果无法获取,则新建连接,

调用RealCall#acquireConnectionNoEvents方法,将复用逻辑设置进connect成员中。

(2)如果没有复用连接,则新创建连接,

我们跟进connect,笔者直接快进到connectSockt,

当连接成功后,获取到source和sink,前者作为接受Response的缓存,后者就是发送缓存,

关于如何发送,上文已经介绍。那么如何接收呢?如下,在Http1ExchangeCodec#AbstractSource中。

跟进到底层有如下代码,笔者在此不再跟进,后面是基本的一些读取相关。有兴趣读者自行了解。

在这个拦截器中,笔者注意到创建或者复用了Connect,接下来,就是通过连接访问服务器了。

6,CallServerInterceptor

通过exchange写入request中的Hearer到缓存区中,

写入请求体什么的,到缓存区中。

最后,调用exchange,finishRequest,通过sink,flush,发送数据并清空缓存区。

随后,通过readResponseHearer,获取Response请求头,

获取请求头完毕后,接下来去读取请求body,

随后赋值给Response,即完成了一次请求。通过将Response链式返回给Chain,最后在AsyncCall中调用onResponse方法,即可通知客户端请求完成。

于是乎,一次请求的过程解读完毕,感谢读者的耐心观看。

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

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

相关文章

git配置用户名和邮箱

1.git 1.配置用户名和邮箱 2.git初体验 git init 初始化git仓库 管理项目让git管理你的本次代码变更 git add .git commit -m “你完成的功能” 后续如果新增/修改/删除代码, 完成新功能时 重复2 3.查看日志 1.git log 4.版本回退 1.查看提交的版本记录 git l…

恒创科技:云服务器公网带宽选择多少合适?有计算公式吗?

随着云计算技术的不断发展,越来越多的企业和个人选择使用云服务器来部署应用和存储数据。而在选择云服务器时,公网带宽是一个重要的参数,它直接影响到服务器的网络性能和数据传输速度。 公网带宽是指云服务器在互联网上的数据传输速率&#x…

内网安全:Exchange服务

目录 Exchange服务 实验环境 域横向移动-内网服务-Exchange探针 一. 端口扫描 二. SPN扫描 三. 脚本探针(还可以探针是否有安全漏洞) 域横向移动-内网服务-Exchange爆破 一 .BurpSuite Intruder模块爆破 域横向移动-内网服务-Exchange漏洞 CVE-2020-17144 Exchange R…

怎样用流程自定义表单提升办公效率?

如果想要提升办公协作效率,可以试试低代码技术平台及流程自定义表单工具。不可否认的是,随着社会的进步和发展,传统的表单制作工具已经没有办法再满足业务量不断上涨的办公需求了,但是,借助专业的流程自定义表单工具就…

4核16G幻兽帕鲁服务器优惠价格表,阿里云和腾讯云报价

幻兽帕鲁服务器价格多少钱?4核16G服务器Palworld官方推荐配置,阿里云4核16G服务器32元1个月、96元3个月,腾讯云幻兽帕鲁服务器服务器4核16G14M带宽66元一个月、277元3个月,8核32G22M配置115元1个月、345元3个月,16核64…

【C++杂货铺】详解类和对象 [中]

博主:代码菌-CSDN博客 专栏:C杂货铺_代码菌的博客-CSDN博客 目录 🌈前言🌈 📁 类的6个默认成员函数 📁 构造函数 📂 概念 📂 特性(灰常重要) &#x1f4c…

恒驰喜讯 | 荣获5项表彰!旭龙乘风起,同心聚沪上,2024年华为上海政企合作伙伴大会成功举办

1月24日,2024年华为上海政企合作伙伴大会暨颁奖典礼在上海成功举办。本次大会以“旭龙乘风起,同心聚沪上”为主题,分为“倾听伙伴声音、传递价值主张、携手伙伴共赢”三个篇章,表彰在2023年度做出卓越贡献的伙伴。上海恒驰信息系统…

RabbitMQ问题总结

:::info 使用场景 异步发送(验证码、短信、邮件。。。)MySQL 和 Redis、ES 之间的数据同步分布式事务削峰填谷… ::: 如何保证消息不丢失 上图是消息正常发送的一个过程,那在哪个环节中消息容易丢失?在哪一个环节都可能丢失 生…

Android创建工程

语言选择Java,我用的Java 最小SDK:就是开发的APP支持的最小安卓版本 Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具,支持 Maven,JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件,转而…

UE4 CustomDepthMobile流程小记

原生UE opaque材质中获取CustomDepth/CustomStencil会报错 在其Compile中调用的函数中没有看到报错逻辑 材质节点的逻辑都没有什么问题,所以看一下报错 在HLSLMaterialTranslator::Translate中 修改之后 mobile流程的不透明材质可以直接获取SceneTexture::customd…

六、Kotlin 类型进阶

1. 类的构造器 & init 代码块 1.1 主构造器 & 副构造器在使用时的注意事项 & 注解 JvmOverloads 推荐在类定义时为类提供一个主构造器; 在为类提供了主构造器的情况下,当再定义其他的副构造器时,要求副构造器必须调用到主构造器…

核对表:使用条件语句CHECKLIST:Using Conditionals

核对表&#xff1a;使用条件语句CHECKLIST&#xff1a;Using Conditionals if-then语句 代码的正常路径清晰吗&#xff1f; if-then 测试对等量分支的处理方式正确吗? 确保不要用“>”代替“>”或用“<”代替“<”。 使用了else子句并加以说明吗&#xff1f; els…

【C++】输入输出、缺省参数、函数重载

目录 C的输入和输出 缺省参数 概念 缺省参数的分类 全缺省参数 半缺省参数 函数重载 概念 C支持函数重载的原理--名字修饰 C的输入和输出 #include<iostream> // std是C标准库的命名空间名&#xff0c;C将标准库的定义实现都放到这个命名空间中 using namespace …

Pandas--安装(2)

安装 pandas 需要基础环境是 Python&#xff0c;Pandas 是一个基于 Python 的库&#xff0c;因此你需要先安装 Python&#xff0c;然后再通过 Python 的包管理工具 pip 安装 Pandas。 使用 pip 安装 pandas: pip install pandas安装成功后&#xff0c;我们就可以导入 pandas …

2023年方便食品行业市场数据分析(电商数据查询):整体销售下滑,但也有品牌逆袭

2023年是疫情全面放开的一年&#xff0c;后疫情时代&#xff0c;人们在生活方式上也有了明显的转变&#xff0c;体现在消费市场中&#xff0c;人们对于特定品类的喜好也发生转变。其中&#xff0c;方便食品市场消费走势的变化便能比较直观的呈现出这一点。 作为疫情期间居家消…

【机器学习300问】15、什么是逻辑回归模型?

一、逻辑回归模型是为了解决什么问题&#xff1f; 逻辑回归&#xff08;Logistic Regression&#xff09;是一种广义线性回归分析模型&#xff0c;尤其适用于解决二分类问题&#xff08;输出为两个类别&#xff09;。 &#xff08;1&#xff09;二分类举例 邮件过滤&#xff…

Caused by: com.mongodb.MongoTimeoutException: Timed out after 30000 ms

报错 Caused by: com.mongodb.MongoTimeoutException: Timed out after 30000 ms while waiting to connect. Client view of cluster state is {typeUNKNOWN, servers[{addressmangodb-m.cc.com:3717, typeUNKNOWN, stateCONNECTING, exception{com.mongodb.MongoSocketReadE…

基于springboot游戏分享网站源码和论文

网络的广泛应用给生活带来了十分的便利。所以把游戏分享管理与现在网络相结合&#xff0c;利用java技术建设游戏分享网站&#xff0c;实现游戏分享的信息化。则对于进一步提高游戏分享管理发展&#xff0c;丰富游戏分享管理经验能起到不少的促进作用。 游戏分享网站能够通过互…

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现 —— 杭州 2024-01-28 code review! 文章目录 C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现1.RTTI、RAII、MVC、MVVM、SOLID简述2.RAII (Resource Acquisition Is Initialization)3.RTTI (Run-Time Type Informat…

2023-2024年重庆职业院校技能大赛“信息安全管理与评估”比赛样题

2023 年重庆职业院校技能大赛&#xff08;高等职业教育&#xff09; “信息安全管理与评估”样题任务书 第一阶段&#xff1a;任务 1 网络平台搭建&#xff08;50 分&#xff09;任务 2 网络安全设备配置与防护&#xff08;250 分&#xff09; 第二阶段&#xff1a;第一部分 网…