没想到我竟然更新得这么频繁,之前有写个一篇OkHttp相关的文章,只不过那篇是带着问题去看源码,只是为了解决问题。这次是带着好奇心来看,想知道它内部到底做了什么。
主要还是以分析源码来分析整个流程,大致的流程我能保证是能讲对的,不会坑爹,但是一些细节上的思想我不敢保证是不是那么一回事。
这份源码是基于okhttp 3.11.0 不保证以后的流程不会改动
一. 调用过程
写个最简单调用Okhttp的方法
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.get()
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
1. 设计分析
(1)OkHttpClient是一个客户端,一个抽象的客户端,所以它内部有引用很多对象,就像我们一个请求的客户端有很多东西。
(2)Request是一个请求数据对象,可以当成一个快递,这个快递有快递内容、寄件人信息、收件人信息等等。
(3)Response 是一个响应对象,和上面差不多,就只包含了响应的内容。
(4)Call 是请求,是一个行为。
那么客户端发送一个请求,这个抽象用代码表示就是okHttpClient.newCall(request)。
请求数据能有很多个,所以Request每次都是new出来,就像寄很多快递每个都不同,请求也有很多个,因为每个请求都是独立的,就像每个快递员帮你寄一个快递,所以Call也是每次newCall都生成不同的对象。但是客户端从抽象的层面讲只有一个,你就是你,是独一无二的,所以从理论上来讲OkHttpClient应该是单例,并且okhttp在使用的时候OkHttpClient确实是全局共用一个,但是我不知道为什么设计者没有把它设计成单例,而是让使用者用单例的形式去使用。
2.简单的引用关系图
二. 从源码分析内部流程
OkHttpClient okHttpClient = new OkHttpClient(); 就不用说了,获取客户端对象。
Request request = ...... ; 也不用说了,打包请求数据
1. newCall方法
Call call = okHttpClient.newCall(request);
往下看
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
看出RealCall是Call的具体实现类,可以参考上面的流程图。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
嗯,每次newCall都是new 一个新的对象,没毛病,符合上面的分析。
构造方法就是一些赋值操作,就不贴代码了
eventListener 是一个监听器,通过钩子告诉调用者当前的流程走到哪步了,是用一个工厂来创建
从这里就能看出newCall方法就只是创建一个RealCall并且做了一些初始化的操作。
2. enqueue
这是具体的请求操作
call.enqueue(callback);
往下看
public void enqueue(Callback responseCallback) {
// 防止同一个call对象重复调用enqueue方法
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
this.captureCallStackTrace();
this.eventListener.callStart(this); // 这里调用钩子通知整个call流程开始
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
我在想这里的synchronized换成使用Atomic是不是会好一点,欢迎评论告诉我。
发现调用了OkHttpClient的Dispatcher的enqueue方法,传了一个AsyncCall对象。
这个AsyncCall是一个CallBack,所以我们可以先不用管它,再回调之后再去看它内部的代码
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);
}
public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(this.name);
try {
this.execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
这个execute是AsyncCall 的方法,我们回调的地方再看,先继续看Dispatcher的enqueue
3.enqueue
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
跳到这个方法里面
synchronized void enqueue(AsyncCall call) {
if (this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
this.runningAsyncCalls.add(call);
this.executorService().execute(call);
} else {
this.readyAsyncCalls.add(call);
}
}
这代码很简单,从它的设计角度出发,它是设计了一个“生产—消费者模型”,至于这个是什么,这里不讲了,自己去找,很简单的。所以设置了两个队列,一个队列runningAsyncCalls表示正在运行,一个队列readyAsyncCalls表示等待运行。如果运行队列满了,那这个请求就进入等待队列。从maxRequests 看出,他设计的默认的队列的大小是64,当然也有提供方法给调用者去改。
放到队列之后执行 this.executorService().execute(call);
public synchronized ExecutorService executorService() {
if (this.executorService == null) {
this.executorService = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return this.executorService;
}
这是一个没有核心线程的线程池,也不用多说了吧,这篇主要讲okhttp,线程池这些就不细说了,也不难。但是从这里能看出,okhttp这个请求流程的核心就是这两个队列+线程池进行调度。
然后等线程被cpu调度之后,就会去执行Runnable的run方法,也就是上面说的AsyncCall的execute方法。
4. AsyncCall 的 execute
protected void execute() {
boolean signalledCallback = false;
try {
Response response = RealCall.this.getResponseWithInterceptorChain();
if (RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
this.responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException var6) {
if (signalledCallback) {
Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var6);
} else {
RealCall.this.eventListener.callFailed(RealCall.this, var6);
this.responseCallback.onFailure(RealCall.this, var6);
}
} finally {
RealCall.this.client.dispatcher().finished(this);
}
}
Response response = RealCall.this.getResponseWithInterceptorChain(); 这是具体的网络请求的过程。所以说,Dispatcher只是负责调度的工作,Call才是具体负责请求操作的。简单来说就是你去食堂打饭,Call才是负责打饭的食堂大妈,Dispatcher只是负责管理排队的保安,你敢插队砍洗你。
然后我们先不管这个请求具体做了什么操作,因为里面的拦截器那些代码逻辑不那么简单,总之先当成做了某部操作得到结果Response ,详解的操作封装得很好,十分的解耦,放心。
得到结果Response 之后调responseCallback的onFailure或者onResponse方法通知调用者网络请求的结果。并且
RealCall.this.client.dispatcher().finished(this); 告诉调度者,这个请求流程到这里已经执行完了。
5. Dispatcher的finished
void finished(AsyncCall call) {
this.finished(this.runningAsyncCalls, call, true);
}
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) {
this.promoteCalls();
}
// 后面的idleCallback是一个扩展吧,暂时不用管
runningCallsCount = this.runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
可以看出这里从运行队列中移除请求之后,调用promoteCalls方法
private void promoteCalls() {
if (this.runningAsyncCalls.size() < this.maxRequests) {
if (!this.readyAsyncCalls.isEmpty()) {
Iterator i = this.readyAsyncCalls.iterator();
do {
if (!i.hasNext()) {
return;
}
AsyncCall call = (AsyncCall)i.next();
if (this.runningCallsForHost(call) < this.maxRequestsPerHost) {
i.remove();
this.runningAsyncCalls.add(call);
this.executorService().execute(call);
}
} while(this.runningAsyncCalls.size() < this.maxRequests);
}
}
}
这里的操作就是判断移除之后的运行队列是否小于最大长度,如果小于,则从准备队列中将第一个请求放到运行队列中然后运行,就永动机了。
这里整个过程就结束了
6. runningCallsForHost方法
单独把这个方法拿出来讲是因为我不敢保证我当前理解就是对的。请求运行之前的enqueue方法,和请求结束之后的finished方法,都有调用这个方法
this.runningCallsForHost(call) < this.maxRequestsPerHost
private int runningCallsForHost(AsyncCall call) {
int result = 0;
Iterator var3 = this.runningAsyncCalls.iterator();
while(var3.hasNext()) {
AsyncCall c = (AsyncCall)var3.next();
if (!c.get().forWebSocket && c.host().equals(call.host())) {
++result;
}
}
return result;
}
(1)首先forWebSocket 这个参数,上面源码中显示传进来的是false,我没具体理解是什么意思,讲真从这命名真看不出来,我感觉是一个开关,提供给扩展用的。
(2)c.host().equals(call.host()))
这个host是HttpUrl的,我的理解是判断相同主机的请求是否一样,一样的话就+1,并且限制最大是5个,也就是说你的url如果是同一个Host,只能同时请求5个,第6个排队去。我不知道这个判断的设计思想是什么,限制这个是为了保证什么?
总结
本来是想一次性写完整个流程的,但我最近在在找工作,这段时间一直在准备比较忙,所以没能写完。所以这次打算分开写,不然我这篇写到一半就要搁着了。这篇文章先介绍了整个okhttp并发的一个过程,具体的请求操作,拦截器那块等总结好了再发出来,只能先说一声抱歉了。
网友评论