一.OkHttp介绍
OkHttp是由Square公司贡献的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。使用它有以下好处
1.支持HTTP2.0并允许对同一主机的所有请求共享一个套接字
2.通过socket连接池,减少了请求延迟
3.默认通过GZip压缩数据
4.响应缓存,避免了重复请求的网络
5.请求失败自动重试主机的其他ip,自动重定向
至于如何实现这些的,相信看完整个OkHttp的介绍,你就清楚了
二.OkHttp的基本使用(以Get请求为例)
引入依赖。
1.首先创建OkHttp(具体实现为OkHttpClient)

2.然后创建请求

3.用OkHttpClient,将请求封装成call

4.执行请求(有两种,execute为同步执行,enqueue为异步执行。这里以同步执行为例)

5.接下来就可以拿response做事了
三.OkHttp的角色分析,以及调用流程
对上述OkHttp的基本使用的介绍可以得出,OkHttp至少涉及四个基本角色,即OkHttpClient,Request,Call,Response。
那么这四个角色的调用顺序是什么呢?
用这张图可以概括

Call之前的就不用过多介绍了,Call之后
请求可以通过execute发出,也可以通过enqueue发出。前者是同步请求,会阻塞线程。后者是异步请求。不管怎么样,都会发出请求。那么请求真正发出之前,会调用一个分发器来调配请求,也就是图中的Dispatcher,将请求分发好了之后,就真正地发出请求,其间会通过拦截器,也就是图中的Interceptors。最后得到响应。响应完了之后,分发器又会去取请求,再真正的发出,以此类推
所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠分发器来调配请求任务。
现在还有点懵?没关系,下面看源码,一步一步地分析,就很清晰了
四.以异步请求为例,研究OkHttp的分发器
在上面的基本使用中可以看到,Request是通过OkHttpClient的newCall方法传入的,我们追踪源码

发现它返回的RealCall对象(RealCall就是Call的一个实现类,Call是一个接口)所以说后面的call.execute或者是call.enqueue都是调用的RealCall的同名方法,因为我们这里以异步请求为例,所以找到RealCall的enqueue方法

我们进入分发器,看看它的enqueue方法怎么处理的AsyncCall

这里的异步请求的处理,可能会有两种情况,一是进入runningAsyncCalls队列,然后直接调用线程池去执行该请求。二是进入readyAsyncCalls队列,等待被执行。用图来表示的话就是这样

那么怎么判断进入哪个队列呢?看if

前者就是,runningAsyncCalls队列的大小是否已经超过临界值(即64)。后者就是,对同一个host(主机)的请求是否超过了临界值(即5)。
前者好理解,后者的意思是,当前在runningAsyncCalls队列中已经有5个对www.baidu.com的请求了,再来一个就是6个了,就不行了。前者是对客户端的保护,后者是对服务器的保护,你细品。那么readyAsyncCalls队列中的任务,何时被执行呢?我们看在runningAsyncCalls.add(call)下面,调用了线程池去执行call。这个call就是一个Runnable,执行call也就是调用call的run方法,call是AsyncCall类,所以是调用AsyncCall的run方法。但是去AsyncCall类中找,没有run方法。所以去AsyncCall的父类当中找

进入NamedRunnable

可以看到有run方法,run方法调用了execute方法,也就是调用了AsyncCall的execute方法

在这里,finally中的代码是一定会执行的。所以就会调用分发器的finished方法,找到该方法

我可以直接说,该方法就是把readyAsyncCalls中的Call移动到runningAsyncCalls中的。那么具体怎么移的呢?
首先调用重载的finished方法。三个参数分别是runningAsyncCalls,刚执行完的请求,true。因为该请求已经执行完了,所以利用remove方法将该请求从runningAsyncCalls队列中移除,之后调用promoteCalls方法,从readyAsyncCalls中取calls。进入promoteCalls方法

这个方法就是具体将readyAsyncCalls中的call移动到runningAsyncCalls中的。代码的意思我已经在注释中写的很清楚了,就不再赘述了
所以,对于异步请求,它的执行过程是这样的

runningAsyncCalls和readyAsyncCalls的数据结构
是双向队列,即ArrayDeque。即两边都可以插入和删除。但是在目前的版本中,貌似没有用到双向队列的功能。我猜是方便后面的扩展。
五.同步请求的过程
进入RealCall的execute方法中

进入executed方法

可以看到它直接将call加入到runningSyncCalls(注意,不是runningAsyncCalls队列)中,按先后顺序一个一个地执行了,这里就不用使用线程池了,因为是一个一个地执行
六.OkHttp对线程池的应用
在上面对异步请求的源码分析中,有这么一段

那么OkHttp对线程池又做了怎样的使用呢?目的是什么?
在分发器中,找到了创建线程池的方法,可以发现两个疑点
第一个是核心线程数为0,最大线程数为Integer.MAX_VALUE
第二个是阻塞队列采用的是SynchronousQueue

为什么这样用呢?
首先看下线程池的三种阻塞队列
①ArrayBlockingQueue 看下面一段代码。
首先创建阻塞队列,容量为1,然后创建核心线程数为0,最大线程数为Integer.MAX_VALUE,且阻塞队列为ArrayBlockingQueue的线程池.。然后分别执行两个任务,第一个任务是死循环,第二个任务是一个输出任务。

这段程序运行的结果为:

只输出了任务1
原因:看线程池的源码

按线程池的执行逻辑,因为核心线程为0,所以会先把任务1加到阻塞队列,然后再判断,当前work的线程为0,所以要创建一个线程,去执行任务1,因为任务1是死循环,所以就跳不出来了。所以任务2会一直待在阻塞队列中,得不到执行。当再来一个任务的时候,即任务三,会出现什么情况呢?

会发现任务1仍然会阻塞,但会先执行任务3,再执行任务2,且任务3和任务2在同一个线程中执行。
原因:因为线程3来的时候,阻塞队列已满,所以又会创建一个新线程,先执行任务三,然后再把阻塞队列当中的任务执行完。
这个阻塞队列,必须得初始化容量
②LinkedBlockingQueue
这个阻塞队列和第一个阻塞队列的底层区别就是,这个阻塞队列是链表。
还有一个区别是,LinkedBlockingQueue可以不初始化容量,当没有初始化的时候,容量就是Integer.MAX_VALUE,所以如果还是上面的代码,加上任务3之后,任务3和任务2都不会执行,都会加入到阻塞队列当中去。
③SynchronousQueue
这个阻塞队列,无容量。这样再配合核心线程数为0,就能达到最大并发量的效果。来一个任务,就能立马执行,而无需等待。当然也不会真的达到Integer.MAX_VALUE,因为有64的限制
使用线程池,就是为了线程复用。当然如果使用前两个阻塞队列,把容量改为0,好像貌似也能达到相同的效果?
解决问题
之所以这么用线程池,就是为了达到无等待,最大并发的效果










