Android OkHttp源码分析--分发器

OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。

OKHttp优点:

1、支持Http1、Http2、Quic以及WebSocket;
2、连接池复用底层TCP(Socket),减少请求延时;
3、无缝的支持GZIP减少数据流量;
4、缓存响应数据减少重复的网络请求;
5、请求失败自动重试主机的其他ip,自动重定向;

OKHttp调用流程:

OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、 Response,但是框架内部进行大量的逻辑处理。

所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠 分发器来调配请求任务。

分发器:内部维护队列与线程池,完成请求调配;

拦截器:五大默认拦截器完成整个请求过程。

用户是不需要直接操作任务分发器的,获得的 RealCall 中就分别提供了 execute 与 enqueue 来开始同步请求或异步请求。无论是同步还是异步请求实际上真正执行请求的工作都在 getResponseWithInterceptorChain() 中。这个 方法就是整个OkHttp的核心:拦截器责任链。

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

分发器:异步请求工作流程

Dispatcher ,分发器就是来调配请求任务的,内部会包含一个线程池。可以在创建 OkHttpClient 时,传递我们 自己定义的线程池来创建分发器。

Dispatcher中的成员有: 

//异步请求同时存在的最大请求
private int maxRequests = 64;
//异步请求同一域名同时存在的最大请求
private int maxRequestsPerHost = 5;
//闲置任务(没有请求时可执行一些任务,由使用者设置)
private @Nullable Runnable idleCallback;
//异步请求使用的线程池
private @Nullable ExecutorService executorService;
//异步请求等待执行队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步请求正在执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步请求正在执行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

同步请求:

synchronized void executed(RealCall call) {runningSyncCalls.add(call);
}

因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录。

异步请求:

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

当正在执行的任务未超过最大限制64,同时 runningCallsForHost(call) < maxRequestsPerHost 同一Host的请求 不超过5个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。

加入线程池直接执行,如果加入等待队列后,就需要等待有空闲名额才开始执行。因此每次执行完 一个请求后,都会调用分发器的 finished 方法

    //异步请求调用void finished(AsyncCall call) {finished(runningAsyncCalls, call, true);}//同步请求调用void finished(RealCall call) {finished(runningSyncCalls, call, false);}private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {//不管异步还是同步,执行完后都要从队列移除(runningSyncCalls/runningAsyncCalls)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();//等待任务想要执行,还需要满足:这个等待任务请求的Host不能已经存在5个了if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}

在满足条件下,会把等待队列中的任务移动到 runningAsyncCalls 并交给线程池执行。所以分发器到这里就完了。 逻辑上还是非常简单的。

分发器线程池:

分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池 来执行。那分发器中默认的线程池是如何定义的呢?为什么要这么定义?

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

在OkHttp的分发器中的线程池定义如上,其实就和 Executors.newCachedThreadPool() 创建的线程一样。首先核 心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。

而最大线 程 Integer.MAX_VALUE 与等待队列 SynchronousQueue 的组合能够得到最大的吞吐量。即当需要线程池执行任务 时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。

一般来说,等待队列 BlockingQueue 有: ArrayBlockingQueue 、 LinkedBlockingQueue 与 SynchronousQueue 。

假设向线程池提交任务时,核心线程都被占用的情况下:

ArrayBlockingQueue :基于数组的阻塞队列,初始化需要指定固定大小。 当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入 队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以 最终可能出现后提交的任务先执行,而先提交的任务一直在等待。

LinkedBlockingQueue :基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。 当指定大小后,行为就和 ArrayBlockingQueu 一致。而如果未指定大小,则会使用默认的 Integer.MAX_VALUE 作 为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会 成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。

SynchronousQueue : 无容量的队列。 使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后 如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任 务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合 Integer.MAX_VALUE 就实现了真正的 无等待。

但是需要注意的时,我们都知道,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不 能无限个数。那么当设置最大线程数为 Integer.MAX_VALUE 时,OkHttp同时还有最大请求任务执行个数: 64的限制。这样即解决了这个问题同时也能获得最大吞吐。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/81868.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华秋亮相2023世界汽车制造技术暨智能装备博览会,推动汽车产业快速发展

洞悉全球汽车产业格局&#xff0c;前瞻业界未来趋势。2023年7月27日-30日&#xff0c;时隔三年&#xff0c;重聚武汉国际博览中心&#xff0c;2023世界汽车制造技术暨智能装备博览会盛大开幕。深耕汽车行业多年的世界汽车制造技术暨智能装备博览会&#xff0c;掀起行业热点新高…

boost beast http server 测试

boost beast http client boost http server boost beast 是一个非常好用的库&#xff0c;带上boost的协程&#xff0c;好很多东西是比较好用的&#xff0c;以下程序使用四个线程开启协程处理进入http协议处理。协议支持http get 和 http post #include <boost/beast/cor…

比较研发项目管理系统:哪个更适合您的需求?

项目管理系统对于保持项目进度、提高效率和确保质量至关重要。然而&#xff0c;市场上众多的研发项目管理系统让许多团队陷入选择困难。本文将对几个主流的研发项目管理系统进行深入分析&#xff0c;以帮助您找到最适合您团队的解决方案。 “哪个研发项目管理系统好用好&#x…

【Linux】常用的文本处理命令详解 + 实例 [⭐实操常用,建议收藏!!⭐]

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

【iPhone】手机还有容量,拍视频却提示 iPhone 储存空间已满

文章目录 前言解决方案 结语 前言 今天在用 iPhone 录像的时候突然提醒我 iPhone储存空间已满 你没有足够的储存空间来录制视频” 可我明明还有 20G 的容量 我非常疑惑&#xff0c;因为我之前还剩1个G都能录像&#xff0c;现在20G反而不行了&#xff0c;于是重启了手机&#…

怎么在JMeter中的实现关联

我们一直用的phpwind这个系统做为演示系统, 如果没有配置好的同学, 请快速配置之后接着往下看哦. phpwind发贴时由于随着登陆用户的改变, verifycode是动态变化的, 因此需要用到关联. LoadRunner的关联函数是reg_save_param, Jmeter的关联则是利用后置处理器来完成. 在需要查…

在线高精地图生成算法调研

1.HDMapNet 整体的网络架构如图所示&#xff0c;最终的Decoder输出三个分支&#xff0c;一个语义分割&#xff0c;一个embedding嵌入分支&#xff0c;一个方向预测。然后通过后处理将这些信息处理成向量化的道路表示。 img2bev的方式之前有IPM&#xff0c;通过假设地面的高度都…

vue新学习 05vue的创建运行原理(vue的生命周期)

01.vue的创建过程 原理解释&#xff1a; 1.定义&#xff1a; 1.Vue的生命周期是指Vue实例从创建到销毁的整个过程中经历的一系列阶段&#xff0c;Vue在关键时刻帮我们调用的一些特殊名称的函数。 2.生命周期函数的名字不可更改&#xff0c;但函数的具体内容是程序员根据需求…

Linux线程同步(条件变量)

文章目录 前言一、条件变量概念二、条件变量相关的函数三、条件变量模拟生产者消费者模型四、使用条件变量的好处总结 前言 本篇文章来讲解一下条件变量的使用。 一、条件变量概念 条件变量&#xff08;Condition Variable&#xff09;是并发编程中一种线程同步机制&#xf…

如何在页面中嵌入音频和视频?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 嵌入音频⭐ 嵌入视频⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏…

如何实现 Java SpringBoot 自动验证入参数据的有效性

Java SpringBoot 通过javax.validation.constraints下的注解&#xff0c;实现入参数据自动验证 如果碰到 NotEmpty 否则不生效&#xff0c;注意看下 RequestBody 前面是否加上了Valid Validation常用注解汇总 Constraint详细信息Null被注释的元素必须为 nullNotNull被注释的元…

chatGLM 本地部署(windows+linux)

chatGLM算是个相对友好的模型&#xff0c;支持中英文双语的对话交流&#xff0c;清华出的 我的教程无需特别的网络设置&#xff0c;不过部分情况因为国内网络速度慢&#xff0c;需要反复重复 chatGLM github地址 一、硬件需求 N卡8G显存以上&#xff0c;最好16G以上&#xff…

AI 绘画Stable Diffusion 研究(六)sd提示词插件

大家好&#xff0c;我是风雨无阻。 今天为大家推荐一款可以有效提升我们使用 Stable Diffusion WebUI 效率的插件&#xff0c; 它就是 prompt-all-in-one&#xff0c; 它不但能直接将 WebUI 中的中文提示词转换为英文&#xff0c;还能一键为关键词加权重&#xff0c;更能建立常…

公众号外包开发框架

公众号开发框架主要指的是在微信公众号平台上开发应用的技术框架。微信公众号是一种基于微信平台的应用&#xff0c;分为订阅号、服务号和企业号&#xff08;现在称为企业微信&#xff09;等不同类型。以下是一些常见的公众号开发框架以及它们的特点&#xff0c;希望对大家有所…

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…

hive修改表或者删除表时卡死问题的解决(2023-08-08)

背景&#xff1a;前阶段在做hive表的改表名时&#xff0c;总是超时&#xff0c;表是内部表&#xff0c;数据量特别大&#xff0c;无论你是修改表名还是删除表都是卡死的状态&#xff0c;怎么破&#xff1f; 终于&#xff1a;尝试出来一个新的方法 将内部表转化成外部表&#…

pytest之测试用例执行顺序

前言 在unittest框架中&#xff0c;默认按照ACSII码的顺序加载测试用例并执行&#xff0c;顺序为&#xff1a;09、AZ、a~z&#xff0c;测试目录、测试模块、测试类、测试方法/测试函数都按照这个规则来加载测试用例。 而 pytest 中的用例执行顺序与unittest 是不一样的&#…

string模拟实现:

string模拟实现&#xff1a; 上一篇博客&#xff0c;我们对String类有了一个基本的认识&#xff0c;本篇博客我们来从0~1去模拟实现一个String类&#xff0c;当然我们实现的都是一些常用的接口。 ❓我们这里定义了一个string类型&#xff0c;然后STL标准库里面也有string&#…

[C#] 简单的俄罗斯方块实现

一个控制台俄罗斯方块游戏的简单实现. 已在 github.com/SlimeNull/Tetris 开源. 思路 很简单, 一个二维数组存储当前游戏的方块地图, 用 bool 即可, true 表示当前块被填充, false 表示没有. 然后, 抽一个 “形状” 类, 形状表示当前玩家正在操作的一个形状, 例如方块, 直线…