前言
已经大火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请求流程
1.创建OkHttpClient客户端.
2.创建Request请求,设置url.
3.通过OkHttpClient的newCall()方法,将Request包装成一个Call接口.
4.最后调用execute()方法得到同步的响应Response.
5.或者调用execute()方法,以CallBack回调接口作为参数,得到异步的响应Response.
POST请求流程
流程和GET请求类似.
不同的地方在于:
1.需要RequestBody请求体封装各种类型的请求参数.
2.调用Request.Builder的post()方法,传入RequestBody,设置为POST请求.
接下来,我们对其内部的流程进行分析
Get请求内部流程分析
第一步:OkHttpClient client = new OkHttpClient();
1.通过OkHttpClient的Builder的默认构造方法来初始化网络所需的各种成员:
这些成员依次为:
Dispatcher:线程池
Proxy:代理
Protocol:协议
ConnectionSpec:连接规则
Interceptor:拦截器
ProxySelector:代理选择器
CookieJar:Cookie缓存
Cache:缓存
SocketFactory:Socket工厂
HostnameVerifier:主机校队
SSLSocketFactory/CertificatePinner:SSL证书相关
ConnectionPool:连接池
等等
2.然后再由Builder将这些成员设置给OkHttpClient对象:
第二步:Request request = new Request.Builder().url("http://xxxxxx").build();
1.通过Request的Builder的默认构造方法,初始化请求部分所需的请求方式method和默认的请求头headers,
2.再由Builder的url()方法设置请求的链接地址,最后调用build()方法返回Request对象。
3.在Builder的build()方法中,调用了Request的构造参数,将method,headers等成员设置给Request对象。
第三步:Call call =client.newCall(request);
1.通过OkhttpClient的newCall()方法,构建一个RealCall对象,且其持有第一,二步创建的OkhttpClient和Request对象的引用。
2.同时RealCall对象还会创建一个拦截器RetryAndFollowUpInterceptor。
第四步:
(一)同步请求:Response response =call.execute();
1.调用第三步的RealCall对象的execute()方法,该方法先检查该RealCall对象是否已经执行过该方法了,重复执行会抛出异常。
2.调用第一步的OkhttpClient的dispatcher()方法,获取到OkhttpClient的线程池Dispatcher,然后执行其executed()方法;
runningSyncCalls是Dispatcher中维护的一个正在执行的同步请求队列,RealCall对象会被加入到该队列的末尾。
3.然后执行RealCall对象的getResponseWithInterceptorChain()方法;
按固定顺序将拦截器(依次为初始化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提供的封装
socket操作就是在这个拦截器里执行的。
f.OkhttpClient的networkInterceptors拦截器,默认是空集.
g.CallServerInterceptor拦截器:向服务器发送请求,将请求header和body写入socket中,然后读取响应header和body,返回最后需要的响应数据.
下图是CallServerInterceptor的intercept()方法的实现
(4)最后的CallServerInterceptor拦截器执行完intercept()方法后,返回请求的响应数据:Response对象.
服务器响应的数据主要通过其中的ResponseBody对象获取。
5.最后不管请求是否成功,最后都会执行Dispatcher的finished()方法,结束整个请求;
同步请求,不会执行Dispatcher的promoteCalls()方法(这个方法在后面的异步请求再分析),通过runningSyncCalls队列的remove()方法将RealCall从运行队列中移除.
(二)异步请求:
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对象只能执行一次。
2.该AsyncCall对象持有第1点中创建的CallBack对象.
3.如果正在执行的异步队列runningAsyncCalls没有超过最大请求数(最大为64)并且该请求的主机的最大请求数没有超过最大限制(最大为5)时,AsyncCall对象会被加入到runningAsyncCalls中;否则,AsyncCall会被加入到准备执行的异步队列readyAsyncCalls中。
4.如果AsyncCall加入了运行队列,会通过Dispatcher的executorService()方法,创建一个单例线程池ThreadPoolExecutor。
使用到的ThreadPoolExecutor的构造参数:
corePoolSize:并发数,maximumPoolSize:最小线程数为0,最大线程数为Integer.MAX_VALUE;
keepAliveTime:空闲线程的存活时间;
workQueue:先进先出的工作队列;
threadFactory:单个线程的线程工厂。
紧接着调用ThreadPoolExecutor的execute()方法,
command就是传入的AsyncCall,然后执行addWorker()方法
在addWorker()方法中,可以发现firstTask(即上述的AsyncCall),被包装成Worker后,再由其内部的Thread执行了start()方法。
5.AsyncCall 继承自NamedRunnable 而NamedRunnable是Runnable接口的抽象实现。
ThreadPoolExecutor的execute()方法执行了工作线程,触发了线程内部的Runnable(即AsyncCall )的run()方法,run()方法内部执行AsyncCall的execute()方法。
6.最后执行的AsyncCall的execute()方法
调用RealCall的getResponseWithInterceptorChain()方法获取最后响应的数据Response(这一步的内部流程和同步请求一样,不再累述)。
如果中途通过retryAndFollowUpInterceptor拦截器取消了请求,或者抛出IO异常,则请求失败,回调responseCallBack(即第1点传入的CallBack接口实现)的onFailure()方法;否则,请求成功,回调responseCallBack的onResponse()方法。
不管请求失败还是成功,都会调用线程池Dispatcher的finished()方法。
异步请求在结束请求时,传入的promoteCalls为true,将改请求从正在执行的异步队列中移除后,会额外执行promoteCalls()方法,检查是否有待执行的请求。
promoteCalls()方法中,如果正在执行的异步队列的请求数小于最大请求数,就会继续检查准备执行的队列中是否有还没有执行的请求,如果有,则取出最早存入准备执行的队列的AsyncCall,只要单个主机的请求数也小于最大请求数,就会重复上述第3点的方法,将这个没执行的请求执行下去。
从上述可以总结这个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封装不同类型的请求参数外。
结语
Okhttp3目前已经有很多通用的第三方封装框架,但是如果配合Rxjava使用,建议使用Okhttp3的同步请求,自己封装一层,可以满足一般项目开发的基本需求。从上面可以看到,其实源码并不是那么难懂,尤其是同步请求,只要多看几遍,即可看到不少Okhttp3的内部运作流程和巧妙之处,这也是建议在配合Rxjava使用时,自己封装一层的原因。当然,如果是配合Retrofit和Rxjava使用,那么就不需要对其过度封装了,因为Retrofit本身就是对Okhttp3的封装库。
共同学习,写下你的评论
评论加载中...
作者其他优质文章