OkHttp源码解析(一)- Dispatcher

2024-02-18 17:32

本文主要是介绍OkHttp源码解析(一)- Dispatcher,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

OkHttp的配置、使用步骤这里就不展开描述了,网络上有很多优秀的文章,这里主要是对学习源码中理解到的知识进行概括总结。

该文章根据OkHttp-3.11.0版本进行分析

首先介绍一下OkHttp的两种请求方式(不是get、post、put等等这些请求方式):

  • 请求方式
    1. 同步
      调用方式:call.execute
      阻塞线程

    2. 异步
      调用方式:call.enqueue
      不阻塞线程
      回调在子线程,不能做更新UI操作

再介绍一下OkHttp中核心的两个东西之一 - Dispatcher,也是这篇文章主要分析的东西:

  • Dispatcher

    意义:分发器,内部维护请求的状态(异步或同步),维护3个队列(ReadyAsyn:异步准备执行队列、RunningAsyn:异步执行中队列、RunningSyn:同步执行中队列),维护一个线程池(核心线程为0,最大线程数为Integer.MAX_VALUE,空闲时间60秒)。这里的线程池,虽然最大线程数定义为了Integer的最大值,但是在执行过程中会将执行的最大并发请求数默认设置为64个,所以

我们先来看看 Dispatcher 是如何构建出来的。
在我们使用OkHttp进行初始化的时候,是通过OkHttpClient的静态内部类 Builder 进行创建(这里使用了建造者模式,有兴趣的可以去看一下设计模式,OkHttp中大量运用了这种设计模式进行对象的创建),Builder 的构造方法中第一步就是创建了 Dispatcher 对象:

// OkHttpClient类
public Builder() {dispatcher = new Dispatcher();//省略其他代码
}//Dispatcher类
public Dispatcher(ExecutorService executorService) {this.executorService = executorService;
}public Dispatcher() {
}

可以看到,Dispatcher 的创建是调用了空参的构造方法,那么里面的线程池是什么时候创建的呢?
我们先看一下 Dispatcher 类中几个关键的属性:

  //并发最大请求数,正在执行的异步请求队列数超过最大请求数时,会把新的请求放在准备的异步请求队列private int maxRequests = 64;//同一Host的并发最大请求数,也就是说假设对于192.168.0.1 最多只能同时请求5次(未完成且未取消状态的请求),超过5个也会把新的同一Host的请求放入准备的异步请求队列private int maxRequestsPerHost = 5;private @Nullable Runnable idleCallback;//线程池,根据注释我们可以知道是用于执行异步请求的,并且是懒加载,也就是用到的时候再初始化/** Executes calls. Created lazily. */private @Nullable ExecutorService executorService;//准备的异步请求队列/** Ready async calls in the order they'll be run. */private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//正在执行的异步请求队列,包含未完成的被取消的请求/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在执行的同步请求队列,包含未完成的被取消的请求/** Running synchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

代码中我添加了中文注释,理解了这几个属性的含义对于接下来看源码的过程有帮助。代码注释中有说到线程池的创建是lazily的,用到再创建,那么我们从OkHttp开始请求时分析:

//同步
Response response = client.newCall(request).execute();//异步
client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {}});

我们点到execute的源码里面看到是 Call 接口,那么必定是实现类做了操作,我们跟踪 newCall() 方法:

  @Override public Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}

可以看到里面构建了一个 RealCall ,不管是同步还是异步,其实都是 RealCall 在执行,那我们就看 RealCall 的 同步/异步 实现:

  //同步@Override public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);try {client.dispatcher().executed(this);Response result = getResponseWithInterceptorChain();if (result == null) throw new IOException("Canceled");return result;} catch (IOException e) {eventListener.callFailed(this, e);throw e;} finally {client.dispatcher().finished(this);}}

从源码我们一眼就能看到 RealCall 的同步方法中将执行的逻辑交给了 Dispatcher,其中同步方法交给 Dispatcher 之后马上就执行 getResponseWithInterceptorChain() 方法了,这里涉及了拦截器链的流程,也是真正执行请求的地方,放到另一篇文章分析。Dispatcherexecuted()

  /** Used by {@code Call#execute} to signal it is in-flight. */synchronized void executed(RealCall call) {runningSyncCalls.add(call);}

可以看到,就是将 RealCall 添加到维护的同步队列中。添加进去了,那么什么时候会将这个call移除呢?在 try catch代码块后面有一个finally代码块,这个代码块中执行了 Dispatcherfinished() 方法,注意,这里传的this是 RealCall 对象,我们跳转进去:

  /** Used by {@code Call#execute} to signal completion. */void finished(RealCall call) {finished(runningSyncCalls, call, false);}

这里传进去的队列是同步队列 runningSyncCalls,并且第三个参数是 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();}
}

根据传参我们可以知道里面代码的执行流程是:1、从同步队列中移除call,移除失败时会抛出异常,然后判断 promoteCalls 参数是否为true,这里同步请求为false,不会调用 promoteCalls() 方法,这个方法在异步请求流程中会被调用。2、判断当前正在运行的请求数是否为0,如果为0并且idleCallback不为空时,会执行这个idleCallback。

接着我们再看异步方法的执行流程:

  //异步@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));}

异步方法中会创建一个 AsyncCall 传入 Dispatcherenqueue 方法中,我们暂且不看 AsyncCall 类,我们先跟踪 Dispatcherenqueue() 方法:

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

方法中做了两个判断:1、判断当前正在执行的请求数是否超过最大请求数限制(源码中初始值为64);2、判断当前同一Host的执行请求数是否超过最大限制数(源码中初始值为5)。

如果判断通过则加入正在执行队列,并且交给线程池去执行,线程池也在此时创建:

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;
}

判断不通过则加入准备队列,等待执行。

到这里我们就明白了线程池是在什么时机创建的,并且也明白了 Dispatcher 在请求过程中扮演了什么样的角色。
紧接着我们还需要弄明白异步请求过程中两个队列(运行中队列、准备队列)是如何配合工作的,Dispatcher中的 enqueue() 方法会利用线程池去执行异步请求,这时我们就需要跳转到 AsyncCall 类查看:

  final class AsyncCall extends NamedRunnable {private final Callback responseCallback;AsyncCall(Callback responseCallback) {super("OkHttp %s", redactedUrl());this.responseCallback = responseCallback;}//省略部分代码@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);}}}

因为 AsyncCall 是一个 NamedRunnable 的实现类,最终会执行到重写的 execute() 方法,方法当中我们又看到了在同步方法中出现过的熟悉的身影 getResponseWithInterceptorChain() ,同样的,我们放到另一个文章中分析。try catch代码块也跟了一个 finally 代码块,这里调用的 finished() 方法中传入的是 AsyncCall 对象,我们跳转进去:

  /** Used by {@code AsyncCall#run} to signal completion. */void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);}

这里调用了3个参数的 finished() 方法,并且传入的第一个参数是运行中的异步队列 runningAsyncCalls ,第三个参数和同步时调用的传参 false 不同,这里为true。我们再看一下重载的3个参数的 finished() 方法, 这里只列出关键的几行代码:

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {//当前分析流程的核心代码synchronized (this) {if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");if (promoteCalls) promoteCalls();}
}

和同步请求的流程一样,会先从运行中的异步队列 runningAsyncCalls 移除call,紧接着因为第三个参数为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<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}//每次for循环的最后再判断运行中队列是否超过最大请求数限制,如果超过就结束循环if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}

这里就是异步的准备队列、运行中队列通过 Dispatcher 相互配合工作的核心代码,至此 Dispatcher 的调度工作就分析完了。

这篇关于OkHttp源码解析(一)- Dispatcher的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/721911

相关文章

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实