Okhttp3-网络请求流程解析

简介: 已经大火2年的Retrofit,必然会提到另外两个库,OKhttp3和Rxjava,尤其前者,作为Retrofit网络请求的底层库,我们有必要了解OKhttp3的网络请求是如何运作的,就会理解为什么OKhttp3比其它网络请求库更高效,为什么Volley,Glide,Picasso在后续版本纷纷改用或支持OKhttp3作为自身网络传输层的底层库。

前言

已经大火2年的Retrofit,必然会提到另外两个库,OKhttp3和Rxjava,尤其前者,作为Retrofit网络请求的底层库,我们有必要了解OKhttp3的网络请求是如何运作的,就会理解为什么OKhttp3比其它网络请求库更高效,为什么Volley,Glide,Picasso在后续版本纷纷改用或支持OKhttp3作为自身网络传输层的底层库。

基本构成

OKhttp3作为Square公司(贡献了很多优秀的开源库,比如Retrofit,OKhttp,Okio,Picasso等)开发,旨于替换Java的HttpUrlConnection和Apache的HttpClient的轻量级网络框架,已经被运用到很多开源库以及Android的源码中(Android Studio 在6.0之后,移除了HttpClient,并且用OKHttp代替了HttpUrlConnection)。

和其它网络框架类似,OKhttp3也主要由以下6个概念一起运作:

1.OkHttpClient:客户端;

2.Dispatcher:线程池;

3.Interceptor:拦截器(OKhttp的特色);

4.Request:请求;

5.Response:响应;

6.CallBack:回调。

Get请求流程

image

1.创建OkHttpClient客户端.

2.创建Request请求,设置url.

3.通过OkHttpClient的newCall()方法,将Request包装成一个Call接口.

4.最后调用execute()方法得到同步的响应Response.

5.或者调用execute()方法,以CallBack回调接口作为参数,得到异步的响应Response.

POST请求流程

image

流程和GET请求类似.

不同的地方在于:

1.需要RequestBody请求体封装各种类型的请求参数.

2.调用Request.Builder的post()方法,传入RequestBody,设置为POST请求.

接下来,我们对其内部的流程进行分析

Get请求内部流程分析

第一步:OkHttpClient client = new OkHttpClient();

1.通过OkHttpClient的Builder的默认构造方法来初始化网络所需的各种成员:

image

这些成员依次为:

Dispatcher:线程池

Proxy:代理

Protocol:协议

ConnectionSpec:连接规则

Interceptor:拦截器

ProxySelector:代理选择器

CookieJar:Cookie缓存

Cache:缓存

SocketFactory:Socket工厂

HostnameVerifier:主机校队

SSLSocketFactory/CertificatePinner:SSL证书相关

ConnectionPool:连接池

等等

2.然后再由Builder将这些成员设置给OkHttpClient对象:


image

第二步:Request request = new Request.Builder().url("http://xxxxxx").build();

1.通过Request的Builder的默认构造方法,初始化请求部分所需的请求方式method和默认的请求头headers,

image


2.再由Builder的url()方法设置请求的链接地址,最后调用build()方法返回Request对象。

image

3.在Builder的build()方法中,调用了Request的构造参数,将method,headers等成员设置给Request对象。

image

第三步:Call call =client.newCall(request);

1.通过OkhttpClient的newCall()方法,构建一个RealCall对象,且其持有第一,二步创建的OkhttpClient和Request对象的引用。

image

2.同时RealCall对象还会创建一个拦截器RetryAndFollowUpInterceptor。


image

第四步:

(一)同步请求:Response response =call.execute();

1.调用第三步的RealCall对象的execute()方法,该方法先检查该RealCall对象是否已经执行过该方法了,重复执行会抛出异常。


image


2.调用第一步的OkhttpClient的dispatcher()方法,获取到OkhttpClient的线程池Dispatcher,然后执行其executed()方法;

runningSyncCalls是Dispatcher中维护的一个正在执行的同步请求队列,RealCall对象会被加入到该队列的末尾。

image


3.然后执行RealCall对象的getResponseWithInterceptorChain()方法;

image

按固定顺序将拦截器(依次为初始化OkhttpClient的拦截器,重试和重定向的拦截器,桥接转换拦截器,缓存拦截器,连接拦截器,初始化OkhttpClient的网络拦截器,服务器回调拦截器)添加到Interceptors集合中,与第二步的Request一起作为构造参数,创建了一个RealInterceptorChain对象。

这里注意构造参数中的(this.index)0,就是RealInterceptorChain的成员变量index,用来标记执行到了Interceptors集合中哪一个拦截器的intercept()方法。

4.紧接着执行RealInterceptorChain对象的proceed()方法;

首先会作判断:if(this.index >= this.interceptors.size()) {

throw new AssertionError();

} else { … },这个判断有什么用呢,先看下面。

因为首次进入httpStream为null,所以不会执行同一请求检查this.sameConnection(request.url())和请求次数的检查this.calls >1.

5.接着看以下三句代码:

RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpStream, connection, this.index +1, request);

Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);

Response response = interceptor.intercept(next);

(1)以Interceptors集合和Request对象,以及index+1作为构造参数,创建一个新的RealInterceptorChain对象;

(2)执行index位置的拦截器的intercept()方法,同时将新创建的RealInterceptorChain对象传递进去,很明显,这个RealInterceptorChain对象的proceed()方法又会被执行,因此,结合第4点的代码判断,通过迭代,Interceptors集合中的所有拦截器都会执行intercept()方法:

a.OkhttpClient的interceptors集合,默认是空集;

b.RetryAndFollowUpInterceptor拦截器:负责请求的重试和重定向,最多20次。

c.BridgeInterceptor桥接拦截器:负责请求构建和响应

d.CacheInterceptor缓存拦截器:负责网络缓存操作

e.ConnectInterceptor连接拦截器:负责socket的IO操作,这里使用了Okio提供的封装


image

socket操作就是在这个拦截器里执行的。

image

f.OkhttpClient的networkInterceptors拦截器,默认是空集.

g.CallServerInterceptor拦截器:向服务器发送请求,将请求header和body写入socket中,然后读取响应header和body,返回最后需要的响应数据.

下图是CallServerInterceptor的intercept()方法的实现

image

(4)最后的CallServerInterceptor拦截器执行完intercept()方法后,返回请求的响应数据:Response对象.

image

服务器响应的数据主要通过其中的ResponseBody对象获取。

image

5.最后不管请求是否成功,最后都会执行Dispatcher的finished()方法,结束整个请求;

同步请求,不会执行Dispatcher的promoteCalls()方法(这个方法在后面的异步请求再分析),通过runningSyncCalls队列的remove()方法将RealCall从运行队列中移除.

image

(二)异步请求:
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {}
@Override
public void onResponse(Call call,Response response) throwsIOException {}
});

与同步请求不同的地方在于

1.执行RealCall对象的的enqueue()方法,需要一个CallBack接口实现作为参数,执行最后请求的成功和失败回调;

该方法内部是调用OkhttpClient的Dispatcher的enqueue()方法,同时传入一个AsyncCall对象作为参数,每个RealCall对象只能执行一次。

image

2.该AsyncCall对象持有第1点中创建的CallBack对象.

image

3.如果正在执行的异步队列runningAsyncCalls没有超过最大请求数(最大为64)并且该请求的主机的最大请求数没有超过最大限制(最大为5)时,AsyncCall对象会被加入到runningAsyncCalls中;否则,AsyncCall会被加入到准备执行的异步队列readyAsyncCalls中。

image

4.如果AsyncCall加入了运行队列,会通过Dispatcher的executorService()方法,创建一个单例线程池ThreadPoolExecutor。

image


使用到的ThreadPoolExecutor的构造参数:

corePoolSize:并发数,maximumPoolSize:最小线程数为0,最大线程数为Integer.MAX_VALUE;

keepAliveTime:空闲线程的存活时间;

workQueue:先进先出的工作队列;

threadFactory:单个线程的线程工厂。

image

紧接着调用ThreadPoolExecutor的execute()方法,

command就是传入的AsyncCall,然后执行addWorker()方法

image

在addWorker()方法中,可以发现firstTask(即上述的AsyncCall),被包装成Worker后,再由其内部的Thread执行了start()方法。


image


image

5.AsyncCall 继承自NamedRunnable 而NamedRunnable是Runnable接口的抽象实现。

image

ThreadPoolExecutor的execute()方法执行了工作线程,触发了线程内部的Runnable(即AsyncCall )的run()方法,run()方法内部执行AsyncCall的execute()方法。

6.最后执行的AsyncCall的execute()方法

调用RealCall的getResponseWithInterceptorChain()方法获取最后响应的数据Response(这一步的内部流程和同步请求一样,不再累述)。

如果中途通过retryAndFollowUpInterceptor拦截器取消了请求,或者抛出IO异常,则请求失败,回调responseCallBack(即第1点传入的CallBack接口实现)的onFailure()方法;否则,请求成功,回调responseCallBack的onResponse()方法。

不管请求失败还是成功,都会调用线程池Dispatcher的finished()方法。

image

异步请求在结束请求时,传入的promoteCalls为true,将改请求从正在执行的异步队列中移除后,会额外执行promoteCalls()方法,检查是否有待执行的请求。


image

promoteCalls()方法中,如果正在执行的异步队列的请求数小于最大请求数,就会继续检查准备执行的队列中是否有还没有执行的请求,如果有,则取出最早存入准备执行的队列的AsyncCall,只要单个主机的请求数也小于最大请求数,就会重复上述第3点的方法,将这个没执行的请求执行下去。

image

从上述可以总结这个Dispatcher的特点:

1.Okhttp3的Dispatcher线程池,同步请求为一个工作对列,异步请求时通过一个工作队列和一个准备队列来互相配合,支持最大64个的并发请求,通过Deque队列先进先出的特点控制请求执行的顺序,而不是通过锁机制;

2.整个Dispatcher内部只创建一个ThreadPoolExecutor,不保存最小的存活线程数,最大线程数为Integer.MAX_VALUE,为每个正在执行的请求创建一个线程,当线程空闲60s后,结束线程;

3.设置有主机数限制,最大每个主机支持5个请求。

在异步请求中,Dispatcher作为第一个接收请求的对象,根据当前正在执行的请求的状况,将新的请求指派到工作队列中并发处理,或者添加到准备队列中缓存起来;不限制单例线程池的最大线程数,减少高并发时额外线程创建的时间耗费,同时不保留最小存活线程数,设置线程空闲60s后销毁,避免资源的长期占用;通过try catch finally 块控制请求队列的执行顺序,而没有使用锁机制,这几个地方的设计都很巧妙。

而Volley的Diapatcher则是由1个缓存线程和默认4个线程的网络线程池组成,线程池采用轮询的机制,这在应对高并发和大数据的请求时并不算高效。

POST请求

内部流程和GET请求基本一样,除了在上述第二步生成的Request对象时,需要额外的RequestBody封装不同类型的请求参数外。

image


结语

Okhttp3目前已经有很多通用的第三方封装框架,但是如果配合Rxjava使用,建议使用Okhttp3的同步请求,自己封装一层,可以满足一般项目开发的基本需求。从上面可以看到,其实源码并不是那么难懂,尤其是同步请求,只要多看几遍,即可看到不少Okhttp3的内部运作流程和巧妙之处,这也是建议在配合Rxjava使用时,自己封装一层的原因。当然,如果是配合Retrofit和Rxjava使用,那么就不需要对其过度封装了,因为Retrofit本身就是对Okhttp3的封装库。这次分析流程比较长,还是建议自己写几个例子后,逐步去分析,会对其内部的流程有一个很明了的认知。

原文发布时间为:2018-07-09
本文作者:lzt橘子
本文来自云栖社区合作伙伴“ 安卓巴士Android开发者门户”,了解相关信息可以关注“ 安卓巴士Android开发者门户”。

相关文章
|
17天前
|
人工智能 监控 安全
NTP网络子钟的技术架构与行业应用解析
在数字化与智能化时代,时间同步精度至关重要。西安同步电子科技有限公司专注时间频率领域,以“同步天下”品牌提供可靠解决方案。其明星产品SYN6109型NTP网络子钟基于网络时间协议,实现高精度时间同步,广泛应用于考场、医院、智慧场景等领域。公司坚持技术创新,产品通过权威认证,未来将结合5G、物联网等技术推动行业进步,引领精准时间管理新时代。
|
1月前
|
机器学习/深度学习 人工智能 算法
深度解析:基于卷积神经网络的宠物识别
宠物识别技术随着饲养规模扩大而兴起,传统手段存在局限性,基于卷积神经网络的宠物识别技术应运而生。快瞳AI通过优化MobileNet-SSD架构、多尺度特征融合及动态网络剪枝等技术,实现高效精准识别。其在智能家居、宠物医疗和防走失领域展现广泛应用前景,为宠物管理带来智能化解决方案,推动行业迈向新高度。
|
26天前
|
网络架构
广播域与冲突域:解析网络技术中的复杂性。
总的来说,理解广播域和冲突域的概念可以使我们在设计或维护网络的过程中,更有效地管理通信流程,避免出现网络瓶颈,提成整体网络性能。就像是如何有效地运作一个市场,把每个人的需求和在合适的时间和地点配对,确保每个人的声音都被听到,每个人的需求都被满足。
48 11
|
17天前
|
机器学习/深度学习 算法 测试技术
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
本文探讨了基于图的重排序方法在信息检索领域的应用与前景。传统两阶段检索架构中,初始检索速度快但结果可能含噪声,重排序阶段通过强大语言模型提升精度,但仍面临复杂需求挑战
53 0
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
|
25天前
|
网络协议 安全 Devops
Infoblox DDI (NIOS) 9.0 - DNS、DHCP 和 IPAM (DDI) 核心网络服务管理
Infoblox DDI (NIOS) 9.0 - DNS、DHCP 和 IPAM (DDI) 核心网络服务管理
54 4
|
2月前
|
人工智能 安全 5G
5G网络安全全解析——新机遇与潜在风险
5G网络安全全解析——新机遇与潜在风险
90 4
|
2月前
|
网络安全
网络问题解析:如何解决CondaHTTPError HTTP 000 CONNECTION FAILED错误。
以上就是斯诺普为你准备的解决Conda出现HTTP连接错误的手术室。希望这辆小车可以顺利驶出棘手的泥潭,再次在自由的大路上疾驰。一切的尝试和努力,只为更好的探索与开发。
125 17
|
3月前
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
343 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
3月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
104 15
|
3月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。

推荐镜像

更多
  • DNS