我们学习线程池的应用 okhttp 这个使用最多的网络库是跑不了的,不难啊,只要大家前面的线程池的内容都了解了,这里没什么难度,都是线程池的应用,谁也比不谁高大上多少,又不是让你写一个,看懂总
是没问题的
寻找 线程池 在 okhttp 的入口
okhttp 使用如下
OkHttpClient().newCall(null).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException?) {
}
override fun onResponse(call: Call?, response: Response?) {
}
})
看到 onFailure,onResponse 大家熟悉不,想到什么没,AsyncTask 看着和这个熟悉不。看到 callback 大家就没联想到 runnable 吗,多线程,futrueTask 都是把我们传进来的 callback 包装在自己的 runnable 中调用,获取数据后回调我们传入 callback 的相关方法,都是这个套路,looper 也是如此,区别是 looper 使用 handle 把任务切换到 mian 线程取去执行
enqueue 添加任务的方法显然是 newCall 出来的对象提供的,我们继续跟进去
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
// RealCall 里的 enqueue 方法
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
在这里大家看见 dispatcher 这个角色了,dispatcher 也叫线程调度器,我们继续深入
// dispatcher.enqueue()
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
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;
}
到这里我们就找到跟了吧,okhttp 内部也是线程池实现的,线程池配置是自己做的,为的还不就是用自己的线程类型呗,要不直接用 Executors.newCachedThreadPool() 了
下面我们继续深入,看到这里可不算完事呢
okhttp Dispatcher 线程调度器设计
Dispatcher 内容不多,看看代码,大家就会霍然开朗的感觉,也不是很难嘛,至少看还是能看懂的
public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable Runnable idleCallback;
// 3个存储任务的容器,并非阻塞队列
private final Deque readyAsyncCalls = new ArrayDeque<>();
private final Deque runningAsyncCalls = new ArrayDeque<>();
private final Deque runningSyncCalls = new ArrayDeque<>();
// 线程池对象和初始化线程对象
private @Nullable ExecutorService executorService;
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 void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
//
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();
}
}
基本代码都在上面了,我们从 线程池构建 -> 3个任务队列 -> 异步任务自行过程 ->
1. 线程池构建
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;
}
这段就是,这个线程池的配置看着是不是和 CachedThreadPool 一样,为什么这么写呢,重点就在于 okhttp 为了使用自己的 Thread 线程类型,所以需要配自己的 threadFactory,那么就得换个写法了
2. 3个任务队列
private final Deque readyAsyncCalls = new ArrayDeque<>();
private final Deque runningAsyncCalls = new ArrayDeque<>();
private final Deque runningSyncCalls = new ArrayDeque<>();
- runningAsyncCalls - 进行中的异步任务对类
- runningSyncCalls - 进行中的同步任务对类
- readyAsyncCalls - 预备的异步任务对类
大家想问为什么这里把任务存储到队列里,直接添加近线程池里执行不就完了吗,okhttp 这里搞个对类出来就是为了用户可以取消任务,FutrueTask 大家知道里面可以用 cancle 取消任务,这里也是同样的思路,所以异步、同步队列的存在大家就可以理解了,但是 readyAsyncCalls 呢,为啥还要有个预备任务队列,这里就得看 okhttp.Dispatcher.maxRequests 这个参数了,虽然线程池是不限线程数量的,但是考虑到移动平台的性能特性,在 Thread 到达 CPU 核心的一定数量时,再开更多的线程反而会带来性能的下降,这是个性能的阀值,maxRequests = 64 就是这个意思,再同时进行超过 64 个任务后,你再添加任务的话就得放到 readyAsyncCalls 里面了,然后在合适的时机添加到线程池里执行任务
3. 多线程任务执行过程
client.dispatcher().enqueue(new AsyncCall(responseCallback));
okhttp 添加任务实际就是这段代码,可以看见 okhttp 把任务包装成 AsyncCall 这个类
final class AsyncCall extends NamedRunnable{...}
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
AsyncCall 我们没看到 run、callback 这写关键的地方,那么关键点在哪里呢,就在于 AsyncCall 继承自 NamedRunnable,而 NamedRunnable 则实现了 Runnable 接口,其 run 方法里面核心就是执行了 AsyncCall 的 execute 方法,好了找到地方了,我们看看 execute 这个方法
@Override 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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
获取 Response 我们就看成执行异步任务的过程,完事后就是 onResponse、onFailure 这2个回调了,成功还是失败,熟悉不熟悉,AsyncTask 不就是这个思路吗。最后则是 finished 方法,finished 方法干啥了我们看看
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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();
}
}
上面使用的一直是 AsyncCall ,所以实际上调的是 finished(runningAsyncCalls, call, true),因为 promoteCalls = true,所以会执行 promoteCalls() 这个方法
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.
}
}
promoteCalls 干啥了呢,遍历一遍 readyAsyncCalls 预备任务队列,把每个预备任务都添加到线程池里去执行,一直到没有预备任务了或是到最大链接数了,这样就形成了任务循环,这样大家就能明白 okhttp 设计3个任务队列的初衷了
4. 取消任务
Dispatcher 里面只有 cancelAll 关闭所有请求
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
我们平时都是给任务添加一个 tag ,通过这个 tag 来吵到 call 来关闭任务
OkHttpUtils.post().url(Const.DATAURL).params(requestMap).tag(getActivity()).build().execute(.....)
public void cancelTag(Object tag) {
if (tag == null) return;
for (Call call : getOkHttpClient().dispatcher().queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
for (Call call : getOkHttpClient().dispatcher().runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
然后经过代码,最后找到远程链接的 sokcet 才能取消请求
public void cancel() {
canceled = true;
StreamAllocation streamAllocation = this.streamAllocation;
if (streamAllocation != null) streamAllocation.cancel();
}
public void cancel() {
HttpCodec codecToCancel;
RealConnection connectionToCancel;
synchronized (connectionPool) {
canceled = true;
codecToCancel = codec;
connectionToCancel = connection;
}
if (codecToCancel != null) {
codecToCancel.cancel();
} else if (connectionToCancel != null) {
connectionToCancel.cancel();
}
}
public static void closeQuietly(Socket socket) {
if (socket != null) {
try {
socket.close();
} catch (AssertionError e) {
if (!isAndroidGetsocknameError(e)) throw e;
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
好了基本就这样,我们看的只是线程池在 okhttp 里面的使用,最后我们在取消任务时可以看到 okhttp 的确是非常复杂的大型库,类很多,角色很多,这里就不是本篇关心的了,后面会详细分析 okhttp 这个库的