Okhttp请求实际使用了okio,当需要进行请求时,会进行一个socket连接,然后将请求报文解析成二进制流进行传输。其中okhttp的关键点就是Dispatcher(分发器)和Interceptor(拦截器)。
Dispatcher 分发器
请求的实际类为Call的实现类RealCall,会调用client.dispatcher()来进行请求。
同步请求 调用:client.dispatcher().executed(this)
这里因为任务为Runnable所有不需要返回状态,直接进行请求
异步请求 调用:client.dispatcher().enqueue(new AsyncCall(responseCallback));
这里使用了这里AsyncCall是Runnable的间接实现子类,需要传递的responseCallback是Callback的子类,这里方便回调请求的结果是成功还是失败。
Dispatcher 维护了三个队列和一个线程池
线程池 executorService
可以看到,如果不用构造器创建一个Executor时会在调用时创建一个,大概参数意思如下:
- 核心线程数:0
- 最大线程数:Integer.MAX_VALUE
- 空闲线程空闲时间:60
- 空闲时间单位:秒
- 阻塞队列:这个队列不保存数据,当用任务添加时会直接运行,如果没有空闲线程则新建一个线程执行
- 线程工厂类:给线程命名
异步请求
等待队列:
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
正在运行队列:
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
当请求任务提交时,会将任务提交到等待或正在运行的队列中去,然后调用线程池中的线程去执行正在运行的请求队列中去,(请求完成和请求失败都算执行完成)执行完成后会使正在运行的队列remove出队任务,然后判断 runningAsyncCalls 队列是否添加 readyAsyncCalls 出队的任务,判断规则如下:
1、队列中数据长度是否 小于64
2、**readyAsyncCalls ** 出队的任务与 **runningAsyncCalls **队列中请求的相同域名是否小于5
异步请求流程:
调用call.enqueue()方法
这里会判断锁上当前请求的RealCall防止异步,首先有个布尔类型的executed,这里默认为false,当这个RealCall进行一次请求后就改为ture,这样第二次请求时会抛出“Already Executed”不允许请求第二次,如果需要再次请求,则需要进行深克隆,因为RealCall间接实现了Cloneable,所有可以调用call.clone()来建立一个新的请求。
这里还有个EventListener监听器是提供给我们来监听网络请求状态的的,可以通过 OkHttpClient和call来创建。
EventListener eventListener = client.eventListenerFactory().create(call);
所以最终会调用事件分发器Dispatcher的enqueue方法来进行网络请求。
client.dispatcher().enqueue(new AsyncCall(responseCallback))方法
这里首先就会进行判断正在运行请求的队列里面是否满足以下要求,如果满足则添加到正在运行的队列中,否则进入等待队列中。然后调用线程池的execute(Runnable),线程池最终会调用任务AsyncCall中的run()方法。
1、队列中数据长度是否 小于64
2、**readyAsyncCalls **出队的任务与 **runningAsyncCalls **队列中请求的相同域名是否小于5
这里回到AsyncCall里面
我们会发现AsyncCall并没有run方法,那么我们先看看他的父类。很简单,在run方法中为线程给定了name,并且调用了子类实现的execute抽象方法。
我们回到AsyncCall,这里有个signalledCallback布尔,为了维持在拦截器中是否没有抛出异常。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
//这里使用责任链模式经过5个拦截器后返回Response
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
//回调任务请求状态
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//最后对finished任务出队
client.dispatcher().finished(this);
}
}
}
同步请求
相对于异步请求就比较简单了,也是用的ReadCall这个类进行的请求,在Dispatcher中维护了一个专门用来同步请求的队列
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
当我们调用call.execute()方法时会调用RealCall的execute()方法,和异步一样,为了不允许用户重复调用使用了executed判断。然后就是请求状态返回的监听,调用evecuted方法会将任务添加到同步请求队列中,然后就直接在当前线程进行x责任链的拦截器进行网络请求
Interceptor 拦截器
重试重定向300-399拦截器:
返回一个Location字段:www.baidu.com(域名),然后重新请求,最多重定向20次,
桥接拦截器:
补全Request请求头
host:域名,
Connetion:Keep-Alive,
Accept-Encoding:gzip,(加密方式,默认为gzip)
Cookie:xxx,
User-Agent:xxx,
Content-Length:请求长度
Content-Type:数据类型
Transfer-Encoding
等等
缓存拦截器:
默认没有,需要OkHttpClient.Builder().cache(new Cache())去设置缓存拦截器
连接拦截器:
准备一个socket连接,准备好后将请求工作交给下一个拦截器
请求服务器拦截器:
将报文传输,接受到response后再将请求冲下往上,最终丢给重试重定义拦截器
等等
缓存拦截器:
默认没有,需要OkHttpClient.Builder().cache(new Cache())去设置缓存拦截器
连接拦截器:
准备一个socket连接,准备好后将请求工作交给下一个拦截器
请求服务器拦截器:
将报文传输,接受到response后再将请求冲下往上,最终丢给重试重定义拦截器