优点:
支持
Http1
、
Http2
、
Quic
以及
WebSocket
连接池复用底层
TCP(Socket)
,减少请求延时
无缝的支持
GZIP
减少数据流量
缓存响应数据减少重复的网络请求
请求失败自动重试主机的其他
ip
,自动重定向
二:OKHttp的使用
1. get请求
异步:

同步:第4步通过 Call.execute() 来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行网络请求

2. post请求
异步:

同步:第5步通过 Call.execute() 来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行网络请求

OKHttp的使用:get请求 和 post请求 ,Call call = client.newCall(request)


在使用
OkHttp
发起一次请求时,使用者最少存在
OkHttpClient
、
Request
与
Call
三个角色。
OkHttpClient
和
Request 的创建,可以使用OkHttp为我们提供的 Builder
(建造者模式)。
OkHttp
在设计时采用的门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端
OkHttpClient
统一暴露出来。
OkHttpClient
中全是一些配置,比如代理的配置、
ssl
证书的配置等。而
Call 本身是一个接口,我们获得的实现 为RealCall.
get请求 和 post请求 ,都执行了client.newCall(request),(client指的是OkHttpClient)
执行的流程是:
OkHttpClient.newCall() ----> RellCall # newRealCall(), 然后是进入RealCall 的enqueue 或execute 方法
如下是 OkHttpClient 的 newCall()的方法

三:Okhttp 源码分析
1. 先分析下 RealCall 的 execute() 同步方法:
首先. 执行OkHttpClient 分发器的executed()方法,如client.dispatcher.executed(this)
其次.发起请求,拿到请求结果,如Response result = getResponseWithInterceptorChain()

2. 接下来分析 RealCall 的 enqueue() 异步方法:
1. 把带有回调的repsonseCallback 封装到AsyncCall对象里,然后把AsyncCall 任务交给
OkHttpClient的分发器,如 client.dispatcher.enqueue(new AsyncCall(responseCallback))

分发器,执行异步任务式,如何把任务添加到 正在执行的队列 还是添加到 等待队列呢?如下:


3. 怎么从 等待队列里取任务 放到 正在执行的队列里呢? 整个流程如下






如果该
RealCall
已经执行过了,再次执行enqueue()方法是不允许的,会抛出异常。异步请求会把一个
AsyncCall
提交给分发器。
AsyncCall
实际上是一个
Runnable
的子类
,
使用线程启动一个
Runnable
时会执行
run
方法,最后执行到
AsyncCall 的
execute
方法
:

同时
AsyncCall
也是
RealCall
的普通内部类,这意味着它是持有外部类
RealCall
的引用,可以获得直接调用外 部类的方法。
可以看到无论是同步还是异步请求实际上真正执行请求的工作都在 getResponseWithInterceptorChain() 中。这个 方法就是整个OkHttp的核心:拦截器责任链。但是在介绍责任链之前,我们再来回顾一下线程池的基础知识。
OKHttp的调用 流程
四:OkHttp分发器线程池
1 .线程池的工作机制
corePoolSize—>BlockingQueue—>maximumPoolSize—>拒绝策略
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务。
4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。


分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池
来执行。那分发器中默认的线程池是如何定义的呢?为什么这样定义呢?
在
OkHttp
的分发器中的线程池定义如上,其实就和
Executors.newCachedThreadPool()
创建的线程一样。
首先核 心线程为0
,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在
60s
内没有工作就会被回收。
而最大线程数 Integer.MAX_VALUE
与等待队列
SynchronousQueue
的组合能够得到最大的吞吐量,即当需要线程池执行任务时,如果没有空闲线程也不需要等待,会新建线程去执行任务!
等待队列的不同指定了线程池的不同排队机制。一 般来说,等待队列 BlockingQueue
有:
ArrayBlockingQueue
、
LinkedBlockingQueue
与
SynchronousQueue
。
2. 使用ArrayBlockingQueue
当设置队列的容量是1,任务1一直没有执行完成,任务会一直在队列里,得不到执行,如下:
假设向线程池提交任务时,核心线程都被占用的情况下:
ArrayBlockingQueue
:基于数组的阻塞队列,初始化需要指定固定大小。
当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。
LinkedBlockingQueue
:基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。
当指定大小后,行为就和
ArrayBlockingQueu
一致。而如果未指定大小,则会使用默认的
Integer.MAX_VALUE
作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。
SynchronousQueue
:
无容量的队列。
(无容量的队列,这样的话,就不会往队列里添加,只要任务数没有达到最大线程数,就会创建新的线程去执行执行任务)
使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败,因为是没有容量的队列,不缓存队列。而失败后如果没有空闲的非核心线程,就会检查当前线程池中的线程数,如果没有达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合 Integer.MAX_VALUE
就实现了真正的
无等待。
但是需要注意的时,我们都知道,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个数。那么当设置最大线程数为 Integer.MAX_VALUE
时,
OkHttp
同时还有最大请求任务执行个数
: 64
的限制。这样即解决了这个问题同时也能获得最大吞吐。
五:拦截器责任链
OkHttp
最核心的工作是在
getResponseWithInterceptorChain()
中进行,在进入这个方法分析之前,我们先来了解什么是责任链模式,因为此方法就是利用的责任链模式完成一步步的请求。
责任链模式
在这种模式中,通常每个接收者 都包含 对另一个接收者的 引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下 一个接收者,依此类推。(A包含B, B包含C,C包含D,依次类推)
那整个过程是什么样子的呢?举例如下:
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
拦截器流程
而
OkHttp
中的
getResponseWithInterceptorChain()
中经历的流程为
请求会被交给责任链中的一个个拦截器。默认情况下有五大拦截器:
1. RetryAndFollowUpInterceptor
第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
2. BridgeInterceptor
补全请求,并对响应进行额外处理
3. CacheInterceptor
请求前查询缓存,获得响应并判断是否需要缓存
4. ConnectInterceptor
与服务器完成
TCP
连接
5. CallServerInterceptor
与服务器通信;封装请求数据与解析响应数据
(
如:
HTTP
报文
)
拦截器详情
一、重试及重定向拦截器
第一个拦截器
:
RetryAndFollowUpInterceptor
,主要就是完成两件事情:重试与重定向。
重试
请求阶段发生了
RouteException
或者
IOException
会进行判断是否重新发起请求。
RouteException
IOException

两个异常都是根据 recover 方法判断是否能够进行重试,如果返回 true ,则表示允许重试。
所以首先使用者在不禁止重试的前提下,如果出现了
某些异常
,并且存在更多的路由线路,则会尝试换条线路进行 请求的重试。其中某些异常
是在
isRecoverable
中进行判断
:

1
、
协议异常
,如果是那么直接判定不能重试
;
(你的请求或者服务器的响应本身就存在问题,没有按照
http
协议来 定义数据,再重试也没用)
2
、
超时异常
,可能由于网络波动造成了
Socket
连接的超时,可以使用不同路线重试。
3
、
SSL
证书异常
/SSL
验证失败异常
,前者是证书验证失败,后者可能就是压根就没证书,或者证书数据不正确, 那还怎么重试?
经过了异常的判定之后,如果仍然允许进行重试,就会再检查当前有没有可用路由路线来进行连接。简单来说,比 如 DNS
对域名解析后可能会返回多个
IP
,在一个
IP
失败后,尝试另一个
IP
进行重试。