1使用流程
1.1同步
1创建OkHttpClient对象,一般通过其内部类Builder创建
2创建Request对象,通过其内部类Builder创建
3通过Request封装Call对象
4通过Call执行execute方法,获取Response
同步请求发出后会进入阻塞状态,知道请求收到响应,所以会阻塞主线程
1.2异步
1创建OkHttpClient对象,一般通过其内部类Builder创建
2创建Request对象,通过其内部类Builder创建
3通过Request封装Call对象
4通过Call执行enqueue方法,需在传入的CallBack中回调onFailure和onResponse方法
1.3区别
1.3.1请求方式
同步:call调用execute,直接返回response
异步:call调用enqueue,需传入CallBack执行onFailure和onResponse回调
1.3.2是否阻塞线程
同步:阻塞当前线程
异步:开启子线程,不阻塞当前线程
2请求流程
2.1同步
2.1.1构建OkHttpClient对象
构建OkHttpClient对象,并为它设置参数。需要通过它的内部类Builder完成。
在Builder中,有两个重要参数,一是dispacher,他是OkHttp中请求的分发器,由它来处理一个请求是执行还是缓存,在同步请求中,dispacher直接将请求加入到队列中去执行。
在Builder另一个重要的参数是connectionPool(连接池)如果每一个客户端与服务器之间叫做一个连接,那么这个连接池是用来存储所有的链接的。而且它实现了哪个连接可以复用,哪个连接可以保持打开等策略。
这里用Builder来构建OkHttpClient对象用到了构造者模式。在Builder的构造方法中实例化一些参数,然后将Builder传入OkHttpClient的构造方法中去。
最后调用Builder的build方法,完成OkHttpClient对象的创建
2.1.2构建Request对象
构建Request对象同样通过其内部类Builder来实现。
在Builder的构造方法中,指定了请求方法为默认的Get,以及创建了Headers的一个内部类Headers.Builder对象来保存一些请求头信息。
在Request.Builder创建完成后,调用它本身的build方法,并将一些参数传递给Request,至此完成Request的创建。
2.1.3构建Call
通过OkHttpClient的newCall方法来完成Call的创建
由于Call是一个接口,所以要通过它的实现类RealCall来实例化
在newRealCall方法中创建了一个RealCall对象,并给他赋值了一个事件监听eventListener。
而在RealCall的构造方法中,将前两部创建的OkHttpClient以及Request对象赋值给了自己,并且创建了一个重定向拦截器。
2.1.4Call执行execute方法
同步请求是通过RealCall的execute方法来执行的。
因为一个同步请求只能执行一次,所以首先在RealCall的execute方法中会判断这个请求是否执行过,如果没有执行过则开始执行,执行过的话抛出异常。
之后会捕捉一些堆栈信息。
然后会将call中的事件监听EventListener打开。
之后(最重要的)由OkHttpClient中的分发器Dispacher将这个请求添加到正在执行的同步请求队列中。
然后调用拦截器链的方法getResponseWithInterceptorCain来获取Response。
最后通过调用dispacher的finished方法来结束请求。
首先将同步请求移除,然后计算正在执行的请求的数量,最后判断如果正在执行的请求的数量为0,并且回调不为空,执行回调的run方法。
至此同步请求完成
2.2异步
2.2.1构建OkHttpClient、Request、Call
流程同2.1.1、2.1.2、2.1.3
2.2.2Call执行enqueue方法
首先是在Call的实现类RealCall中执行enqueue方法。
在RealCall的enqueue方法中,首先判断这个请求是否执行过,如果执行过就抛出异常,没有执行过的话就继续往下执行。
之后同样捕捉一些堆栈信息和开启事件监听。
最后调用OkHttpClient中的dispacher的enqueue方法来执行异步请求。
在调用dispacher的enqueue方法时,会将传进来的CallBack对象封装成一个AsycnCall对象,这个AsycnCall其实是一个Runnable
在dispacher执行enqueue方法时,先判断正在执行异步请求对列中的请求数量是否小于允许最大请求数量(64)以及当前主机的请求数量是否小于每个主机允许的最大请求数量(5)。如果满足条件,则将该请求加入到正在执行异步请求队列中,如果不满足,就将该请求加入到缓存队列中。
在加入到正在执行的异步请求队列后,就会执行executorService().execute(call)
executorService()是去获取一个线程池
这里最大线程数量设置成Integer的最大值是因为在dispacher中已经设置了最大运行请求数量为64
获取到线程池后,执行它的execute方法,将call对象传入,就会执行call的run方法。
但是在Call的实现类AsycnCall中没有run方法,于是到它的父类NamedRunnable中找到run方法,其实就是包装了一层,执行的就是它的子类AsycnCall的execute方法
在AsycnCall的execute方法中,是通过拦截器链来获取Response。
之后判断重定向拦截器是否取消,如果取消了,执行CallBack的onFailure方法,未取消的话执行CallBack的onResponse将Response返回,需要注意的是onFailure和onResponse都是在子线程执行的。
最后执行dispacher的finished方法。
首先将请求在正在执行请求队列中移除,之后调整队列中的请求,最后计算正在执行的请求的数量,最后判断如果正在执行的请求的数量为0,并且回调不为空,执行回调的run方法。
3、任务调度核心类Dispacher
3.1Dispacher的作用
通过维护同步或者异步请求的状态来管理它们。并在其内部维护了一个线程池来执行请求。
3.2Dispacher如何维护请求的
3.2.1同步
在RealCall的同步请求(execute)中有两个地方用到了dispacher
第一处,将同步请求加入到正在执行的同步请求队列中
第二处,请求执行完之后将请求移除出请求对列
3.2.2异步
在RealCall的异步请求(enqueue)中,有一个地方用到了dispacher
首先判断正在执行异步请求对列中的请求数量是否小于允许最大请求数量(64)以及当前主机的请求数量是否小于每个主机允许的最大请求数量(5)。如果满足条件,则将该请求加入到正在执行异步请求队列中,如果不满足,就将该请求加入到缓存队列中。
之后通过dispacher内部的线程池ExcutorService来执行这个请求
之后执行了AsycnCall的execute方法,在地内部执行完请求后,又通过dispacher移除了这个请求。
在移除请求后,又执行了promoteCalls()方法,重新调度runningAsycnCalls以及readyAsycnCalls两个请求对列
3.2.3ExcutorService
dispacher内部的线程池在创建的时候设置了一些条件
第一个表示核心线程数为0,这样做是为了在空闲一段时间后将所有的线程销毁。
第二个表示可以添加的最大线程数为Int最大值,理论上可以无限添加线程,但是dispacher的最大线程数为64,所有无影响。
第三个表示当前线程数大于最大核心线程数时,多余的空闲线程在60内被销毁。
3.2.4promoteCalls()
异步请求的调度
这里最重要的两点是
一是将缓存请求队列里优先级最高的一个请求移除
二是将移除的缓存添加到正在执行的请求队列当中
之后线程池再次执行它里面的线程
4、OkHttp拦截器
4.1拦截器
4.1.1拦截器的作用
可以对请求进行拦截、验证、返回等操作
4.1.2拦截器的分类
应用拦截器、OkHttp内部拦截器、网络拦截器
4.2拦截器链
4.2.1什么叫做拦截器链
在OkHttp内部,将所有的拦截器封装成一个链条,并依次调用使其发挥其相应的功能。
4.2.2拦截器链的依次调用
无论是同步还是一部请求,获得返回的Response都是通过方法getResponseWitInterceptorChain()方法,在其内部就封装了一个包含了所有拦截器的集合,然后利用这个集合封装一个拦截器链,最后调用拦截器链的proceed()方法。
上边五个红框中是OkHttp内部的五个拦截器。
在proceed()方法内部,经过一层包装,调用了InterceptorChain内部的proceed()方法,在这个方法中,最主要的就是创建了一个新的拦截器链,有一个参数很重要是index + 1,然后调用当前拦截器链中对应的index的拦截器的intercept方法。
以Interceptor实现类RetryAndFllowUpInterceptor的intercept()方法为例,里面又调用了上一步新创建的拦截器链的process()方法,所以拦截器链中的拦截器会依次执行。
4.3重连重定向拦截器 RetryAndFllowUpInterceptor
4.3.1作用
请求失败的时候进行重连,但是有次数限制,最多20次,一需要满足其内部的一些条件才可以重连。
4.3.2拦截流程
1、创建streamAllocation
streamAllocation是用来建立执行网络http请求时的网络组件的,但是它并不在这个拦截器中使用,而是依次传递到了ConnectionInterceptor中使用。
2、在while(true)循环中,调用连接器链的process()方法,获取上一个拦截器的返回值response,并根据条件判断是否重连。
3、如果满足条件不需重连了,就将response返回。
当捕获到异常或者通过followUpRequest()方法验证返回的response又返回了request时,说明请求有错误需要重连,直至请求正常或者重连次数达到20次
4.4桥接拦截器BridgeInterceptor
4.4.1作用
设置内容长度、编码方式、压缩等头部信息
4.4.2拦截流程
- 将用户构建的Request转化为能进行网络访问的请求,其中一个头信息为Keep-Alive,保持tcp连接不中断。
- 调用拦截器链的process()方法,获取下一个拦截器返回的response。
- 如果客户端与服务端都支持gzip压缩,则将服务端返回的response解压并返回。
4.5缓存策略
4.5.1缓存的目的
使下一次请求的时候能节省时间
4.5.2使用方法
在创建OkHttpClient的时候为其添加Cache类
创建cache类需要两个参数,第一个是File,指定缓存路径;第二个是大小,指定缓存最大容量。
4.6使用put方法将缓存写入磁盘
Cache的put方法负责将信息写入缓存,它返回一个CacheRequest。同时OkHttp内部有一个负责清理缓存的线程池专门负责自动清理缓存
- 首先根据返回信息response中包含的请求信息,判断是不是GET请求,如果不是的话不进行缓存。
- 如果满足条件,则将返回的response封装成实体类Entry,使用DiskLruCache将response与request的头信息写入缓存。在写入缓存的时候,如果是https请求,同时保存握手信息。
- 利用CacheRequest的实现类CacheRequesrImpl,将返回体response的实体写入缓存。
4.7使用get方法从磁盘读取缓存
Cache的get方法负责将磁盘中的缓存读取出来,并以Response方式返回
- 首先根据传入的request中包含的url信息得到相应的key,根据key由cache去缓存中取一个快照Snapshot,如果没取到的话直接返回null
- 如果取到了Snapshot,则把其封装成Entry实体,再通过Entry去获取Response。
- 看request与response是否是成对出现的,如果是就将response返回。
4.8缓存拦截器
4.8.1作用
处理网络数据的缓存,什么时候用缓存数据,什么 时候读取网络数据等等。
4.8.2拦截流程
- 如果缓存不为null,去缓存中获取缓存响应。
2.根据第一步得到的缓存响应,去封装缓存策略类
2.1、在缓存策略类中有两个变量,一个是netWorkRequest,在网络上发送的请求,当不需要访问网络的请求数据的时候,netWorkRequest为null。
另一个是cacheResponse,要返回或验证的缓存响应。当不适用缓存的时候,cacheResponse为null
2.2、在CacheStrategy的工厂类的get方法中,主要就是根据各种条件判断netWorkRequest与cacheResponse是否为空,分为三类情况。
2.2.1、 netWorkRequest不为null,cacheResponse为null
这时主要满足的条件是
缓存响应为null,也就是cacheResponse为null、
是https请求,并且缓存的响应缺少必要的握手、
请求或者响应不允许缓存、
2.2.2、 netWorkRequest为null,cacheResponse不为null
这时主要满足的条件是缓存响应可用或者缓存可用但是缓存时间过长
2.2.3、 netWorkRequest不为null,cacheResponse不为null
这时需要封装一个Request,让后将他和缓存响应一起作为参数传递到CacheStrategy的构造方法中
3.如果缓存不为空,调整缓存命中数
4.如果缓存中缓存响应不为空,缓存策略中缓存响应为空,关闭缓存中的缓存响应
5.如果禁止使用网络并且没有缓存响应,创建一个504的响应并返回
6.如果不需通过访问网络获取数据,直接返回缓存的响应
7.如果以上条件都不满足,调用拦截器链的process,去下一个拦截器获取响应
8.如果获取下一拦截器的响应后,如果缓存策略中的缓存响应不为空,则将访问响应与缓存响应同时封装成新的响应,并跟新到缓存后再返回
9.如果获取网络响应后,缓存不为空,将网络响应写入缓存后返回,如果访问方法不允许缓存,直接再将缓存删除
4.9网络连接拦截器
4.9.1作用
打开与服务器的连接,正式开启OkHttp的网络请求。
4.9.2拦截流程
- 在拦截器链中获取resquest和streamAllocation,并通过streamAllocation得到HttpCodec和RealConnection。
HttpCodec是编码Request和解码Response用的
RealConnection是进行实际网络IO传输的
- 执行连接器链的process方法,将HttpCodec和RealConnection传递给后面的拦截器。
4.9.3newStream方法
- 由findHealthyConnection生成一个RealConnection,并由它创建一个HttpCodec,然后返回。
2.在findHealthyConnection方法中,进行while(true)循环,首先通过findConnection方法获取RealConnection对象,然后判断它的successCount是否为0,是的还表示网络连接完成,直接返回,不是的还判断连接是否健康,不健康进行关闭资源操作后进行下一次循环。
3.findConnection方法中,首先去尝试复用连接,如果StreamAllocation中存在可复用连接则复用
4.如果不能复用,在连接池中获取新的连接并进行网络连接
5.将连接再放入连接池中
6.在connect方法中首先检查连接是否已经建立了,如果是,则抛出异常。判断的标志位protocol表示整个连接建立及使用过程中所用到的协议。
7.创建ConnectionSpec的集合,ConnectionSpec的作用是指定Socket的连接配置。以及创建ConnectionSpecSelector。
8.判断是否需要进行Tunnel连接
4.10连接池的put、get方法
4.10.1连接池的作用
管理连接的复用、实现哪些连接保持打开状态的策略
4.10.2get方法
1、Get方法遍历连接池中的连接,获取 一个可用的传递给
streamAllocation的acquir方法,然后返回这个连接。
2.acquir方法中,首先将在连接池获取的连接赋值给
StreamAllocation的成员变量connection,然后 再将一个StreamAllocation的弱引用添加到connection的allocation集合中,这个几个的size越大,表示这个连接的负载越大。
4.10.3put方法
- 首先异步回收连接,然后将连接添加到队列中
4.11连接池的自动回收
- cleanUpRunnable作为独立线程清理连接池。
- 每次清理完成后需要返回下一次清理的时间
3.线程池会等待释放锁和时间片,之后再次进行清理
4.在cleanup方法中,根据GC回收算法清理连接,关键要看如何标记出最不活跃的连接。
在purneAndGetAllocationCount方法中,去判断哪个连接最不活跃。
首先是对RealConnection中维护StreamAllocation弱引用的集合allocations进行遍历。
如果在StreamAllocation的弱引用中得到了StreamAllocation则进行下一次循环,如果得不到就将这个弱引用在集合中删除。
每次循环后判断一下这个存放弱引用的集合是否为空,是的话直接返回0。
如果直到循环结束没有返回0的话,则返回集合的size。