系列
OKHttp3–详细使用及源码分析系列之初步介绍【一】
OKHttp3–流程分析 核心类介绍 同步异步请求源码分析【二】
OKHttp3–Dispatcher分发器源码解析【三】
OKHttp3–调用对象RealCall源码解析【四】
OKHttp3–拦截器链RealInterceptorChain源码解析【五】
OKHttp3–重试及重定向拦截器RetryAndFollowUpInterceptor源码解析【六】
OKHttp3–桥接拦截器BridgeInterceptor源码解析及相关http请求头字段解析【七】
OKHttp3–缓存拦截器CacheInterceptor源码解析【八】
OKHttp3-- HTTP缓存机制解析 缓存处理类Cache和缓存策略类CacheStrategy源码分析 【九】
回顾
在上一篇解析OKHttp同步异步请求文章中,已经大概介绍了Dispatcher这个类,它在OKHttp中的地位非常重要,一定要对它的作用理解透;作为整个框架的任务调度员,我们会有一些疑问,比如
-
Dispatcher到底是什么,干啥用的?
在OKHttp中Dispatcher就是用来保存并管理用户产生的同步请求(RealCall)和异步请求(AsyncCall), 并负责执行AsyncCall
-
OKHttp是如何实现同步以及异步请求的?
没有别的,就是靠Dispatcher将我们发送的请求进行管理 在同步请求时,将请求添加到runningSyncCalls中(正在执行的同步请求队列); 在请求结束时从队列移除该请求 在异步请求时,将封装成AsyncCall的异步请求线程根据条件添加到runningAsyncCalls队列(正在执行的异步请求队列) 和readyAsyncCalls队列(等待执行的异步请求队列),然后执行AsyncCall;在请求结束时,从队列移除, 并调整两个队列的元素
Dispatcher
这里我们先不摆源码,先说它有什么特点,让大家有个总体上的认识
数据结构
在Dispatcher中维护了三个队列,队列类型是ArrayDeque,这是一个双端队列,即可以从队列头部和尾部进行数据操作,能够实现FIFO原则,即先进去的可以先执行,不过不是线程安全的实现,多线程环境中需要加锁;看过AsyncTask源码的肯定知道它,具体细节可参考从源码解析-Android数据结构之双端队列ArrayDeque 实现FIFO和LIFO队列
-
第一个队列是runningSyncCalls,是一个正在执行的同步请求队列,所有我们添加的同步请求都会被添加到这里面,包括已被取消但没执行完的请求,队列泛型是RealCall对象
-
第二个队列是runningAsyncCalls,是一个正在执行的异步请求队列,所有我们添加的异步请求都会被添加到这里,包括已被取消但没执行完的请求,队列泛型是AsyncCall对象,实现了Runnable接口
-
第三个队列是readyAsyncCalls,是一个等待执行的异步请求队列,什么意思呢?因为OKHttp允许同时执行的异步请求数量必须在64个以内,且单个host同时执行的最大请求数量在5个以内,所以当我们添加的异步请求数超过它,该请求就会被添加到该队列,等runningAsyncCalls队列有空闲位置后添加到里面
对于单个host同时执行的最大请求数量不理解的童靴,这里说明下,host在这里指的是hostname,即主机名(代表一个主机,每个主机都有一个唯一标识,即ip地址,但是每个主机的主机名并不一定是唯一的),你可以理解为同时往同一个服务器上发送的请求数量不能超过5个;不过OKHttp是通过URL的主机名判断的,所以对同一个ip地址的并发请求仍然可能会超过5个,因为多个主机名可能共享同一个ip地址或者路由(相同的HTTP代理)
有的人可能说了,用这些队列有什么用呢?好处在哪呢?
要知道通过这些队列,OKHttp可以轻松的实现并发请求,更方便的维护请求数以及后续对这些请求的操作(比如取消请求),大大提高网络请求效率;同时可以更好的管理请求数,防止同时运行的线程过多,导致OOM,同时限制了同一hostname下的请求数,防止一个应用占用的网络资源过多,优化用户体验
线程池
OKHttp在其内部维护了一个线程池,用于执行异步请求AsyncCall,其构造如下
private ExecutorService executorService;
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;
}
我们需要关注的就是ThreadPoolExecutor前三个参数
- 第一个是0,说明该线程池没有核心线程,所有线程都是工作线程,即所有线程超过一定空闲时间会被回收
- 第二个参数是Integer.MAX_VALUE,即最大线程数,虽然设置值这么大,但是无须担心性能消耗过大问题,因为有队列去维护请求数
- 第三个参数是60,即工作线程空闲60s后就会被回收
关于线程池更多信息可参考Android开发-通过ExecutorService构建一个APP使用的全局线程池
请求管理
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
在类里定义了这两个变量,其含义是默认支持的最大并发请求数量是64个,单个host并发请求的最大数量是5个;这两个值是可以通过后续设置进行更改的,并且这个要求只针对异步请求,对于同步请求数量不做限制
当异步请求进入Dispatcher中,如果满足上面两个数量要求,该请求会被添加到runningAsyncCalls中,然后执行它;如果不满足就将其添加到readyAsyncCalls中;当一个异步请求结束时,会遍历readyAsyncCalls队列,再进行条件判断,符合条件就将请求从该队列移到runningAsyncCalls队列中并执行它
不管是同步请求还是异步请求最终执行完后都会从队列中移除
除了添加和移除,OKHttp还支持用户取消添加过的请求,可以全部取消,即将队列清空;也可以取消某一个请求
源码
最后我们通过源码来了解Dispatcher
public final class Dispatcher {
//最大并发请求数
private int maxRequests = 64;
//单个主机最大并发请求数
private int maxRequestsPerHost = 5;
private Runnable idleCallback;
/** 执行AsyncCall的线程池 */
private ExecutorService executorService;
/** 等待执行的异步请求队列. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** 正在执行的异步请求队列,包含已取消但为执行完的请求 */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** 正在执行的同步请求队列,包含已取消但为执行完的请求 */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
//构造方法 接收一个线程池
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
//构建一个线程池
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;
}
/**
* 设置并发执行的最大请求数
*/
public synchronized void setMaxRequests(int maxRequests) {
if (maxRequests < 1) {
throw new IllegalArgumentException("max < 1: " + maxRequests);
}
this.maxRequests = maxRequests;
promoteCalls();
}
public synchronized int getMaxRequests() {
return maxRequests;
}
/**
* 设置每个主机同时执行的最大请求数
*/
public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
if (maxRequestsPerHost < 1) {
throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
}
this.maxRequestsPerHost = maxRequestsPerHost;
promoteCalls();
}
public synchronized int getMaxRequestsPerHost() {
return maxRequestsPerHost;
}
/**
* 当分发器处于空闲状态下,即没有正在运行的请求,设置回调
*/
public synchronized void setIdleCallback(Runnable idleCallback) {
this.idleCallback = idleCallback;
}
/**
* 执行异步请求
* 当正在执行的异步请求数量小于64且单个host正在执行的请求数量小于5的时候,就执行该请求,并添加到队列
* 否则添加到等待队列中
*/
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
/**
* 取消所有请求
*/
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
//调整请求队列,将等待队列中的请求放入正在请求的队列
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // 如果正在执行请求的队列已经满了,那就不用调整了.
if (readyAsyncCalls.isEmpty()) return; // 如果等待队列是空的,也不需要调整
//遍历等待队列
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//单个host正在执行的请求数量小于5的时候,将该请求添加到runningAsyncCalls中并执行它
//同时从等待队列中删除它
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // 如果正在执行请求的队列已经满了,就退出循环
}
}
/** 返回单个host的请求数 */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) result++;
}
return result;
}
/** 执行同步请求,只是将其添加到队列中 */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
/** 异步请求执行完成调用. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
/** 同步请求执行完成调用. */
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();
}
}
/** 返回当前正在等待执行的异步请求的快照. */
public synchronized List<Call> queuedCalls() {
List<Call> result = new ArrayList<>();
for (AsyncCall asyncCall : readyAsyncCalls) {
result.add(asyncCall.get());
}
return Collections.unmodifiableList(result);
}
/** 返回当前正在执行的异步请求的快照. */
public synchronized List<Call> runningCalls() {
List<Call> result = new ArrayList<>();
result.addAll(runningSyncCalls);
for (AsyncCall asyncCall : runningAsyncCalls) {
result.add(asyncCall.get());
}
return Collections.unmodifiableList(result);
}
//返回等待执行的异步请求数量
public synchronized int queuedCallsCount() {
return readyAsyncCalls.size();
}
//计算正在执行的请求数量
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
}
可以说Dispatcher的源码比较简单,不难理解,需要把握住关键,它的作用就是负责对用户的请求进行管理,保存,备份,重点在于使用线程池执行异步请求,对于同步请求基本不做多少处理