注册

OkHttp源码分析

 大家好,我是小黑,一个还没秃头的程序员~~~


路是走出来的,而不是空想出来的。


相信大家找工作的时候都会被问及到Okhttp的原理以及源码分析,好记性不如烂笔头,所以这次我打算把它记录下来方便日后复习查看,也和大家分享一下,如果有什么不对之处还请大家多多指教!


这次的分析分为三个步骤:



  1. 网络请求发出去后到了哪里
  2. 请求是怎么被处理的
  3. 请求结束后又会做什么

(一)网络请求发出去后到了哪里


OkHttp发送请求有两种方式:enqueue/execute,在enqueue异步请求之前我们需要调用newCall()newCall() 返回的是RealCall对象


 /**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}

所以我们找到RealCall类中的enqueue()


  @Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

可以看到最终是执行了dispatcher().enqueue() 来完成的,我们进入Dispatcher类中,Dispatcher类是用来实现任务调度的,主要有以下变量


  //最大并发请求数
private int maxRequests = 64;
//每个主机的最大请求数
private int maxRequestsPerHost = 5;

//消费者线程池
private ExecutorService executorService;

//等待中的请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

//正在运行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

//正在运行的同步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

Dispatcher类中,有两个构造函数,一个有传入线程池,一个没有传入线程池,没有传入线程池的话会在异步请求之前创建一个默认的线程池


 public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}

public Dispatcher() {
}

//创建线程池,SynchronousQueue为一个没有容量的队列
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;
}

前面讲到执行RealCallenqueue() 便会最终到Dispatcherenqueue() ,它的代码如下


 synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时,把请求加载到runningAsyncCalls队列中中并在线程池中执行,否则就加入到readyAsyncCalls队列中进行等待。到此为止,第一个问题就解决了,请求最终会被发送到两个队列中,要么被执行要么等待。


(二)请求是怎么被处理的


任务被放进队列中后,任务是AsyncCall类,是继承于NamedRunnable的一个类,执行AsyncCallexecute() 方法,代码如下:


    @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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}

上面的代码中,getResponseWithInterceptorChain() 返回了response,并在相应的回调中返回,这是处理请求的地方,这里会添加一个拦截器链,如是否重定向,缓存拦截器,自定义拦截器等,代码如下:


  Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//自定义拦截器
interceptors.addAll(client.interceptors());
//重定向拦截器
interceptors.add(retryAndFollowUpInterceptor);
//桥接拦截器,设置请求头中的属性的
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存拦截器,有缓存就会取缓存,没有缓存再去连接服务器
interceptors.add(new CacheInterceptor(client.internalCache()));
//连接拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}

//最后一个拦截器,会对服务器进行网络调用,在intercept()方法中构建头部以及body,并获取返回
interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}

接下来我们看代码最后面的proceed() 方法,这个方法是RealInterceptorChain这个类实现的,代码如下:


  @Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}

我们接着点进去 ,在这里面会去做关于response的返回,在这里从拦截器列表中取出拦截器,并使用通过各个拦截器通过intercept()方法的实现来获取拦截器的调用返回,拦截器是按顺序取出来处理的,代码如下:


  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
...

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

...
return response;
}

到这里,问题二已解决了,请求是在getResponseWithInterceptorChain() 获取返回的。


(三)请求结束后又会做什么


上面提到任务执行的时候会调用到AsyncCallexecute() 方法,方法最后会调用client.dispatcher().finished(this) ,我们点进finished() 方法看一下,代码如下:


  void finished(AsyncCall call) {
finished(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) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}

if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}

关键在于promoteCalls() 方法,代码如下:


  private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // 运行中的请求以及到了最大的请求数
if (readyAsyncCalls.isEmpty()) return; // 没有等待中的请求了
for (Iterator<AsyncCall> 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.
}
}

从代码中可以看出,在一个请求结束后,会去对运行中的请求以及等待中的请求数进行判断,并将等待队列中的请求移除一个并添加到线程池中进行处理,即进入运行队列中处理,这就是请求结束后的内容了,到此为止,OkHttp源码分析的三个步骤就介绍完毕,日后我会接着分享自己在阅读源码的体会与总结,最后,希望喜欢我文章的朋友们可以帮忙点赞、收藏、评论,也可以关注一下,如果有问题可以在评论区提出,谢谢大家的支持与阅读!


作者:移动端开发_小黑
链接:https://juejin.cn/post/7102072027409809422
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册