OkHttp完全解读

news2024/11/21 2:29:06

一,概述

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.coloradmin.cn/o/1416810.html

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

相关文章

sqli-labs靶场第七关

7、第七关 id1 --单引号报错,id1" --双引号不报错,可以判断是单引号闭合 id1) --也报错,尝试两个括号闭合,id1)) --不报错 接下来用脚本爆库 import stringimport requestsnumbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] letters2 list(string.ascii_…

二、Gradle 与 Idea 整合

这里写自定义目录标题 1、Groovy简介2、Groovy 安装3、创建 Groovy 项目4、Groovy 基本语法 1、Groovy简介 详细了解请参考:http://www.groovy-lang.org/documentation.html 2、Groovy 安装 下载后解压到本地 验证: groovy的安装情况 3、创建 Groo…

231. Power of Two(2 的幂)

题目描述 给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。 如果存在一个整数 x 使得 n 2 x n 2^x n2x,则认为 n 是 2 的幂次方。 问题分析 题目要求的是给定一个数判断…

[ESP32 IDF] wifi 的应用

目录 背景知识 wifi的基本连接使用 WiFi篇—— WiFi两种模式文章中二、WiFi 的启动(STA 及 AP 模式) 输出现象 通过websocket控制LED 实践验证 实验现象 背景知识 WIFI是ESP32非常重要的一个功能,想要使用一下IDF的API实现将ESP32连…

Golang Playground: 轻松提升你的技能

探索、实验和学习 Go 语言 Golang Playground 是一个在线工具,允许用户在方便且友好的环境中实验、练习和提升他们的编码技能。无论是初学者还是开发人员,Golang Playground 都提供了一个无需本地安装的环境,可以轻松编写、编译和执行 Go 代…

第四篇:怎么写express的路由(接口+请求)

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📘 引言: &#x1f4…

防御保护第六天笔记

一、防火墙的用户认证 用户、行为、流量 --- 上网行为管理三要素 防火墙管理员登录认证的作用有两点:检验身份的合法性,划分身份权限 用户认证 --- 上网行为管理的一部分 用户认证分类有以下三类: 1、上网用户认证 --- 三层认证 --- 所有的…

k8s 进阶实战笔记 | Scheduler 调度策略总结

文章目录 Scheduler 调度策略总结调度原理和过程调度策略nodeSelect亲和性和反亲和性NodeAffinify亲和验证PodAffinity 亲和验证PodAntiAffinity 反亲和验证污点与容忍跳过 Scheduler 调度策略 调度策略场景总结 Scheduler 调度策略总结 调度原理和过程 Scheduler 一直监听着…

拼多多砍价群2024年最新群聊分享

分享最新拼多多现金助力互助微信群138个,井然有序打发时间,拼多多互点不求人,#拼多多互助群#一起来相互助力! ​拼多多互助砍价群免费助力互助群,拼多多助力群免费微信,识别下方二维码进群。拼多多助力群免…

仅需这条指令解决 sudo 报错或将用户添加到 sudoers

解决 sudo 报错或将用户添加到 sudoers 仅需这条指令 既然找到了这里,我只想通过查找了整整一天得到的经验和教训告诉你答案,不需要 nano、vim 这类的编译器,也不需要 chmod 更改 /etc/sudoers 文件只读权限,只需要控制台终端在 …

文心一言 VS ChatGPT :谁是更好的选择?

前言 目前各种大模型、人工智能相关内容覆盖了朋友圈已经各种媒体平台,对于Ai目前来看只能说各有千秋。GPT的算法迭代是最先进的,但是它毕竟属于国外产品,有着网络限制、注册限制、会员费高昂等弊端,难以让国内用户享受。文心一言…

【Redis】关于它为什么快?使用场景?以及使用方式?为何引入多线程?

目录 1.既然redis那么快,为什么不用它做主数据库,只用它做缓存? 2.Redis 一般在什么场合下使用? 3.redis为什么这么快? 4.Redis为什么要引入了多线程? 1.既然redis那么快,为什么不用它做主数据…

【论文解读】Object Goal Navigation usingGoal-Oriented Semantic Exploration

论文:https://devendrachaplot.github.io/papers/semantic-exploration.pdf 代码:https://github.com/devendrachaplot/Object-Goal-Navigation 项目: Object Goal Navigation using Goal-Oriented Semantic Exploration example&#xff1…

找不同-《企业应用架构模式》2024典藏版

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 以下是2004年《企业应用架构模式》中译本和2024年《企业应用架构模式》典藏版译本的页面。 您能从中找出至少10处不同吗? 如何选择UMLChina服务 UMLChina公众号精选&…

Python代码耗时统计

time模块 在代码执行前后各记录一个时间点,两个时间戳相减即程序运行耗时。这种方式虽然简单,但使用起来比较麻烦。 time.time() 函数返回的时间是相对于1970年1月1日的秒数 import timestart time.time() time.sleep(1) end time.time() print(f&…

任正非最新讲话:没有退路就是胜利之路!

内容来源:本文来自心声社区 组织管理 9月4日,华为心声社区发布了华为创始人任正非在华为高端技术人才使用工作组对标会上的讲话。 任正非表示,先有专才,才有全才,要实现跨界交流、融合创新,让领袖自然成长…

实验3:数据显示输出

1、实验目的: 掌握将内存单元存储的数据显示输出到显示器的方法。 2、实验内容: 将内存单元存储的字节数据(例如 56H)的16进制数的低位输出到显示器并显示。 3、实验要求: (1)运行程序后&a…

mac安装mysql的8.0设置面板启动不了

1、前言 记得之前安装mysql5.7的时候,是可以直接从设置里面的mysql面板启动的,但是到了mysql8.0之后就启动不了了,这个问题不知道是版本问题还是我换了m系列芯片的mysql导致的,之前很多次都启动不了,这次搞了下&#x…

计算机提示缺失dll文件怎么办?那种dll解决方法更值得推荐

当在运行游戏,软件程序的过程中遇到“找不到dll”的情况时,这实际上意味着系统或应用程序无法定位并加载必要的动态链接库文件(DLL),从而无法顺利完成预期的功能调用和执行流程。这种问题的发生可能会引发一系列严重后…

使用自有数据集微调ChatGLM2-6B

1 ChatGLM2-6B介绍 ChatGLM是清华技术成果转化的公司智谱AI研发的支持中英双语的对话机器人。ChatGLM基于GLM130B千亿基础模型训练,它具备多领域知识、代码能力、常识推理及运用能力;支持与用户通过自然语言对话进行交互,处理多种自然语言任…