本篇文章主要对OkHttp进行分析,主要内容如下:
- OkHttp初识
- OkHttp基本执行流程(Dispatcher)
- 拦截器(Interceptor)
- 责任链设计模式
1. OkHttp 初识
OkHttp 在开发中经常使用到,常见的用法是:
okhttpclient okhttpclient = new okhttpclient();
request request = new request.builder()
.url("www.baidu.com")
.get()//get 请求
.build();
call call = okhttpclient.newcall(request);
call.enqueue(new callback() {
@override
public void onfailure(call call, ioexception e) {
//nop
}
@override
public void onresponse(call call, response response) throws ioexception {
//nop
}
});
上述代码是基于异步的网络请求,同步的网络请求不同的地方是:
try {
response execute = call.execute();
} catch (ioexception e) {
//nop
}
不相同的地方点在于call对象调用的方法,那么,同步调用与异步调用主要的差别是什么?
call.execute()
方法将直接进行网络请求,阻塞当前线程直到获得网络请求的响应。异步执行会将call放入一个异步执行队列中,由executorservice在后台进行执行。
2. 基本执行流程
基本执行流程主要谈的是call的执行流程,在call的执行流程中分为同步执行流程与异步执行流程。call其实是一个继承了cloneable的接口,我们调用call call = okhttpclient.newcall(request);
获取的其实是realcall这个对象,因此下文提到的call其实就是realcall。
2.1 Call的同步执行流程
通过realcall.excute()
方法执行的流程为:
- client.dispatcher().executed(this);向client的dispatcher中注册当前call;
- getresponsewithinterceptorchain();执行网络请求,在经过一系列拦截器之后获取响应结果;
- client.dispatcher().finished(this);向client的dispatcher中注销当前call。
2.2 Call的异步执行流程
通过realcall.enqueue(callback)
方法执行的流程首先是,client.dispatcher().enqueue(new asynccall(responsecallback));
创建一个asynccall,将enqueue的callback传递过去。asynccall其实是一个runnable,当调用enqueue()
方法的时候就是讲asynccall传递给dispatcher。dispatcher里面封装了一个线程池去执行这个call,executorservice().execute(call);
,这个call就是一个runnable,我们跟进这个runnable的run()方法,我们可以看到里面执行了一个execute()
;所以逻辑最终回到了asynccall的execute()方法。realcall.asynccall.execute()
与同步执行的流程有些类似:
- response response = getresponsewithinterceptorchain(); 执行网络请求获取网络请求的结果;
- responsecallback 返回执行结果;
- client.dispatcher().finished(this); 向dispatcher中注销当前请求的call。
2.3 Dispatcher干了些啥
通过上面call的执行流程,我们可以看出其实okhttp的核心其实是dispatcher这个分发器,接下来我们详细分析okhttp中dispatcher在网络请求的时候做了些什么事情。
2.3.1 同步Dispatcher
首先看下代码:
/** used by {@code call#execute} to signal it is in-flight. */
synchronized void executed(realcall call) {
runningsynccalls.add(call);
}
从上面的代码可以看出,大体的执行逻辑就是将传递过来的realcall添加进了一个队列中,那么这个runningsynccalls到底是什么?看下源码:
/** running synchronous calls. includes canceled calls that haven't finished yet. */
private final deque<realcall> runningsynccalls = new arraydeque<>();
在call同步执行的过程中,dispatcher仅仅将call放进了runningsynccalls这个队列,其他的什么都没有做,这个队列包含了正在执行的call,而将call注册该队列主要的作用是方便全局管理运行的call。
2.3.2 异步Dispatcher
首先我们看看代码:
synchronized void enqueue(asynccall call) {
if (runningasynccalls.size() < maxrequests && runningcallsforhost(call) < maxrequestsperhost) {
runningasynccalls.add(call);
executorservice().execute(call);
} else {
readyasynccalls.add(call);
}
}
在异步调度中,传递过来的asynccall是被放在线程池中进行处理的。这个线程池是什么?来看看代码:
public synchronized executorservice executorservice() {
if (executorservice == null) {
executorservice = new threadpoolexecutor(0, integer.max_value, 60, timeunit.seconds,
new synchronousqueue<runnable>(), util.threadfactory("okhttp dispatcher", false));
}
return executorservice;
}
默认该线程池是一个不限制大小的线程池。从前面代码我们可以看到,dispatcher会限制每一个host请求的最大限制,private int maxrequestsperhost = 5;
同时也会限制同时执行的最大请求数量,private int maxrequests = 64;
。在runningasynccalls队列中,保留全部满足限制条件而正在被executorservice执行的所有asynccall,而不满足限制条件的则由readyasynccalls进行保存。
在异步调度中如果当前的call不能立即入队执行的话,那么会执行readyasynccalls.add(call);
这个方法不会立即执行而是需要进行等待 ,进入等待则是说明当前的情况是要么有64条线程正在并发,要么同一个地址有5 个请求,那么等待的call什么时候会被再次执行呢?
他的触发条件为:
1. Dispatcher 的setMaxRequestPerHost() 方法被调用时候 ;
2. Dispatcher 的setMaxRequests() 被调用时候;
3. 当有一条请求结束了 执行了finish()的出队操作, 这个时候会触promoteCalls()方法执行 ,从而进行调整。�
2.3.3 dispatcher的finished()
在finished()中我们看看其实现原理:
/** used by {@code asynccall#run} to signal completion. */
void finished(asynccall call) {
finished(runningasynccalls, call, true);
}
/** used by {@code call#execute} to signal completion. */
void finished(realcall call) {
finished(runningsynccalls, call, false);
}
private <t> void finished(deque<t> calls, t call, boolean promotecalls) {
int runningcallscount;
runnable idlecallback;
synchronized (this) {
if (!calls.remove(call)) throw new assertionerror("call wasn't in-flight!");
if (promotecalls) promotecalls();
runningcallscount = runningcallscount();
idlecallback = this.idlecallback;
}
if (runningcallscount == 0 && idlecallback != null) {
idlecallback.run();
}
}
同步或者异步dispatcher的finished()
方法最后都会执行到finished()
方法,在该方法中除了会从runningsyncalls队列中移除当前正在被执行的call,异步方法还会检查由于限制条件(这里的限制条件是指最大请求数与单个host最大的请求数量)而保存在readyasyncalls队列中的asynccall从而进行移除。在异步方法中关键代码:finished(runningasyncalls, call, true);
第三个参数就是判断是否需要移除readyasyncalls中的call。
private void promotecalls() {
if (runningasynccalls.size() >= maxrequests) return; // already running max capacity.
if (readyasynccalls.isempty()) return; // no ready calls to promote.
for (iterator<asynccall> i = readyasynccalls.iterator(); i.hasnext(); ) {
asynccall call = i.next();
if (runningcallsforhost(call) < maxrequestsperhost) {
i.remove();
runningasynccalls.add(call);
executorservice().execute(call);
}
if (runningasynccalls.size() >= maxrequests) return; // reached max capacity.
}
}
在promotecalls()中,主要的逻辑就是如果当前线程大于maxRequest则不进行操作,如果小于maxRequest则遍历整个readyAsyncCalls,取出一个call,并把这个call放入runningAsyncCalls,然后执行execute。如果在遍历过程中runningAsyncCalls超过maxRequest则不再添加,否则一直添加。总结一下,promoteCalls()负责ready的Call到running的call的转换,在finished()
方法中,所有的call,不管是realcall或者asynccall都会在执行完毕之后检测是否存在正在进行的http请求,检测的方法为:
public synchronized int runningcallscount() {
return runningasynccalls.size() + runningsynccalls.size();
}
当判断runningcallscount为0的时候,且该idlecallback存在的时候,回调idlecallback的run()方法。那么idlecallback什么时候存在?或者说调度器什么时候处于空闲状态?继续分析源码:
/**
* set a callback to be invoked each time the dispatcher becomes idle (when the number of running
* calls returns to zero).
*
* <p>note: the time at which a {@linkplain call call} is considered idle is different depending
* on whether it was run {@linkplain call#enqueue(callback) asynchronously} or
* {@linkplain call#execute() synchronously}. asynchronous calls become idle after the
* {@link callback#onresponse onresponse} or {@link callback#onfailure onfailure} callback has
* returned. synchronous calls become idle once {@link call#execute() execute()} returns. this
* means that if you are doing synchronous calls the network layer will not truly be idle until
* every returned {@link response} has been closed.
*/
public synchronized void setidlecallback(@nullable runnable idlecallback) {
this.idlecallback = idlecallback;
}
从上面方法中追溯到,调度器的空闲状态在异步方法调度时,在callback的onResponse()
方法或者onfailed()
方法被回调的时候,调度器处于空闲状态。同步方法中只有在excute()
方法返回的时候才会处于空闲状态。
3. 拦截器
在okhttp中真正核心的除了上面提到的dispatcher还有就是拦截器interceptor。在同步请求方法或者异步请求方法中,都会执行一条很重要的指令getresponsewithinterceptorchain()
该方法就是拦截器的入口方法,接下来源码分析该方法:
response getresponsewithinterceptorchain() throws ioexception {
// build a full stack of interceptors.
list<interceptor> interceptors = new arraylist<>();
interceptors.addall(client.interceptors());
interceptors.add(retryandfollowupinterceptor);
interceptors.add(new bridgeinterceptor(client.cookiejar()));
interceptors.add(new cacheinterceptor(client.internalcache()));
interceptors.add(new connectinterceptor(client));
if (!forwebsocket) {
interceptors.addall(client.networkinterceptors());
}
interceptors.add(new callserverinterceptor(forwebsocket));
interceptor.chain chain = new realinterceptorchain(interceptors, null, null, null, 0,
originalrequest, this, eventlistener, client.connecttimeoutmillis(),
client.readtimeoutmillis(), client.writetimeoutmillis());
return chain.proceed(originalrequest);
}
从上面源码中可以看到,在一次网络请求中其实是经历了多次拦截器的调用,首先先用一个流程图来大体的分析各个拦截的调用过程:
从上面的流程可以看出,okhttp调用了多个拦截器来对一个请求做拦截操作,那么这样的操作是怎么完成的呢?其实在每一个拦截器后面都会调用RealInterceptorChain.proceed()来进行处理,回到上面的源码,okhttp将所有的拦截器添加进入了一个集合之中,利用自增递增来调用RealInterceptorChain.proceed()
来依次处理添加进入的拦截器。
跟进下RealInterceptorChain的源码:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//...异常判断省略
// 获取下一个拦截器.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec
, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
RealInterceptorChain 构造函数中的index与interceptor进行对应,通过interceptor.intercept(next)
方法在各个拦截器里面又能通过next参数继续调用proceed()
方法,完成递归调用。至于各个拦截器具体的功能,相信大家都大概清楚,这里就不多阐述了。
4. 责任链设计模式
在okHttp中拦截器部分设计到的核心就是责任链设计模式,首先回顾下什么是责任链模式:使多个对象都有机会处理 同一个请求,从而避免请求的发送者与接收者之间的耦合关系。将这些对象连接成一条链,并沿着这条链传递 请求,直到有一个对象处理它为止。
现在我们来剖析OkHttp中的调用链,回顾下在OkHttp中的调用链结构:
上面的调用模式核心是通过Interceptor来进行完成的,该接口主要的作用为添加、移除、转换请求或者回应头部信息
。瞟一眼该接口的源码:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
//省略
}
}
Interceptor 中intercept(Chain chain)
方法负责具体的过滤,而它子类中又调用了Chain,
该Chain其实是指RealInterceptorChain
,在RealInterceptorChain
中持有了一个interceptor
的集合 ,通过递归调用该List中的拦截器, 对发起的请求进行层层处理,最后递归结束返回请求结果。
4.1 具体分析责任链模式
上面大体的说明了在OkHttp中责任链的调用,现在详细分析该责任链怎么完成调用。责任链模式其实分为完全责任链模式与不完全责任链模式。完全责任链模式是指: 当请求到达某个处理类的时候,要么处理,要么传递给下个处理类
。不完全责任链模式: 当请求到达时候, 处理一部分( 可以配置一些参数或者 增加请求头等),然后扔给下一个处理类进行处理。
在Okhttp中,调用的责任链就是不完全的责任链模式。责任链模式的特点:
1. 抽象一个处理任务类;
2. 任务处理类持有下一个任务对象;
3. 完全责任链模式处理请求任务时类似于if-else,不完全责任链模式则对一个请求进行分步处理。
在OkHttp中,发起的请求经过拦截器的层层处理最终返回结果,它具体是怎么操作的? 回溯到Okhttp的 getResponseWithInterceptorChain()
该方法在okHttp中同步或者异步最终调用的方法,也是责任链调用的起始方法。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 添加各种拦截器
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
接下来介绍核心类RealInterceptorChain
,该类是整个责任链的调度中心,该类继承了Interceptor
的内部接口Chain
。
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
//省略...
request()
获取的是当前的请求,而proceed()
就是负责具体的转发任务, 看看RealInterceptorChain
中的proceed()
方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
// 省略..
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//省略..
return response;
}
该方法核心的就这么几步:
1. 根据传入进来的责任链集合和索引获取下一个责任链(next) ,即每一个拦截器会持有下一个链对象;
2. 从拦截器集合中获取当前的拦截器;
3. 拦截处理下一个责任链(该链中传入当前的请求,只是索引+1 ,那么执行的逻辑就是当前的拦截器如果处理了就直接返回,如果没有执行或者执行了部分,那么就封装一个新的请求对象,由下一个链进行处理,下一个链又对应一个拦截器,同理,如果处理了就直接返回,没有处理了话,下一个拦截器处理一部分再次扔给拦截器集合中的下一个拦截器,这样进行递归调用,直到返回结果)。
具体调用如下:
整体流程可以看下图:
上述图片描述的就是责任链的调度过程,关于OkHttp的后续内容,后面文章继续。
如果文章中有什么疏漏或者错误的地方,还望各位指正,你们的监督是我最大的动力,谢谢!