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开发者门户”。

相关文章
|
22天前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
34 0
|
24天前
|
机器学习/深度学习 算法 PyTorch
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
RPN(Region Proposal Networks)候选区域网络算法解析(附PyTorch代码)
177 1
|
27天前
|
缓存 网络协议 Linux
【Shell 命令集合 网络通讯 】Linux 配置DNS dnsconf 命令 使用教程
【Shell 命令集合 网络通讯 】Linux 配置DNS dnsconf 命令 使用教程
38 0
|
29天前
|
消息中间件 Unix Linux
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
Linux进程间通信(IPC)介绍:详细解析IPC的执行流程、状态和通信机制
43 1
|
1月前
|
数据采集 数据可视化 大数据
Python在数据科学中的实际应用:从数据清洗到可视化的全流程解析
Python在数据科学中的实际应用:从数据清洗到可视化的全流程解析
34 1
|
7天前
|
存储 安全 测试技术
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
14 0
|
24天前
|
SQL 安全 网络安全
构筑数字堡垒:网络安全漏洞解析与防御策略
在数字化时代,网络安全已成为维护信息完整性、保障用户隐私和确保商业连续性的关键。本文将深入探讨网络安全领域的核心议题—安全漏洞及其防御机制。通过分析常见网络攻击手段,如SQL注入、跨站脚本攻击(XSS)及拒绝服务(DoS)攻击,揭示其背后的原理与潜在危害。同时,文章将重点介绍加密技术的种类和应用场景,以及如何通过强化安全意识,构建多层次的防御体系来有效预防和应对网络安全威胁。本研究旨在为读者提供一份系统性的网络安全防护指南,帮助个人和组织在不断演变的威胁面前保持警惕,并采取适当的安全措施。
19 2
|
29天前
|
域名解析 缓存 网络协议
探索Qt 网络编程:网络地址与服务类全解析
探索Qt 网络编程:网络地址与服务类全解析
54 0
|
1月前
|
数据采集 前端开发 JavaScript
Java网络爬虫实践:解析微信公众号页面的技巧
Java网络爬虫实践:解析微信公众号页面的技巧
|
1月前
|
运维 监控 网络虚拟化

推荐镜像

更多