今天看了okhttp的源码,做一下记录。比较low,也是无数人写过的内容了,但是自己看过总要留下点笔记。
一个最简单的网络请求框架应该具有两个基本要素:执行网络请求的类(httpurlconnection那种)和执行任务的线程池。
那么自然就引出了网络请求任务的管理策略问题,okhttp是搞了一个Dispatcher来做任务管理的类。
Dispatcher中有两个ArrayDeque的队列,runningAsyncCalls(正在执行的网络请求)和readyAsyncCalls(将要执行的等待队列)来管理异步请求的队列。(注:ArrayDeque内部包含了一个数组和两个代表头尾的指针,是线程不安全的,因此效率高于LinkedList。此外,在源码中看到,ArrayDeque还可以在iterator遍历时指定移除某一个元素。厉害了我的ArrayDeque)
在我们写call.enqueue时,调用的是RealCall的enqueue方法,
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到是调用了client.dispatcher().enqueue方法将AsyncCall包裹的callback加入了队列。进入dispatcher类可以看到:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到,运行中的队列最大容量为maxRequests (默认值64),同一个host最大并发是maxRequestsPerHost(默认值5)。
回到AsyncCall类中查看具体的执行过程:
它继承自 NamedRunnable、Runnable,其run方法执行以下代码:
protected void execute() {
boolean signalledCallback = false;
try {
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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
先不管上面那个拦截器,可以看到大致是判断了是否取消、是否失败、是否有IO异常,回调onfailure方法,否则就回调onResponse了。
finally 执行这个方法: client.dispatcher().finished(this);到Dispatcher的finished方法中:
private void finished(Deque 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();
}
}
可以看到把call从runningAsyncCalls移除了。同时执行下面的方法:
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator 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.
}
}
从readyAsyncCalls中取出一个加入到runningAsyncCalls中,同样是经历上面的判断,空闲就立刻执行。
哦,对了,执行的executor也是放在dispatcher中的,具体初始化代码如下:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
可以看到是一个单例的设计模式,把synchronized关键字加在了方法上。该线程池的keepalivetime是60s,使用的是不存储元素的阻塞队列SynchronousQueue。也就是我们最常用的ExecutorService newCachedThreadPool()的代码实现。
总结一下就是:
我们得到的Call是一个RealCall,调用enqueue方法来把任务加入到Dispatcher的请求队列中。
根据当前正在进行中的网络请求个数和设置的最大网络请求数的情况来决定是加入请求队列还是等待队列。
请求队列中的任务每完成一个,就会自动把等待队列中的任务加入到请求队列中。
Call的cancel方法并不是把请求从队列中立即移除,而是将其标记为已取消状态,等轮到它执行时根据这个标记直接回调onfailure方法。
网络请求任务管理策略就是这样,下一篇我们看一下okhttp的拦截器。