当前位置: 首页 > news >正文

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的限制。这样即解决了这个问题同时也能获得最大吞吐。

相关文章:

Android OkHttp源码分析--分发器

OkHttp是当下Android使用最频繁的网络请求框架&#xff0c;由Square公司开源。Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp&#xff0c;同时现在流行的Retrofit框架底层同样是使用OKHttp的。 OKHttp优点: 1、支持Http1、Http2、Quic以及Web…...

大数据面试题:说下Spark中的Transform和Action,为什么Spark要把操作分为Transform和Action?

面试题来源&#xff1a; 《大数据面试题 V4.0》 大数据面试题V3.0&#xff0c;523道题&#xff0c;679页&#xff0c;46w字 可回答&#xff1a;Spark常见的算子介绍一下 参考答案&#xff1a; 我们先来看下Spark算子的作用&#xff1a; 下图描述了Spark在运行转换中通过算…...

【图像去噪的扩散滤波】基于线性扩散滤波、边缘增强线性和非线性各向异性滤波的图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

python函数、运算符等简单介绍2(无顺序)

list&#xff08;列表&#xff09; 列表是Python的一种内置数据类型&#xff0c;列表是可以装各种数据类 型的容器 # 第一种list创建方式 list_name [晓东,小刚,明明,小红,123,123.4,123] print(list_name) print(type(list_name)) # 输出结果&#xff1a; [晓东, 小刚, 明明…...

k8s 自身原理 3

前面有分享到 master 主节点上的 四个组件&#xff0c;etcd&#xff0c;ApiServer&#xff0c;scheduler&#xff0c;controller manager 接下来我们分享一波 woker 节点上的组件&#xff0c;xdm 还记得 worker 节点上都有什么吗&#xff1f; kubeletkube-proxy实际的服务对应…...

SpringBoot 3自带的 HTTP 客户端工具

原理 Spring的HTTP 服务接口是一个带有HttpExchange方法的 Java 接口&#xff0c;它支持的支持的注解类型有&#xff1a; HttpExchange&#xff1a;是用于指定 HTTP 端点的通用注释。在接口级别使用时&#xff0c;它适用于所有方法。GetExchange&#xff1a;为 HTTP GET请求指…...

Spring Boot多级缓存实现方案

1.背景 缓存&#xff0c;就是让数据更接近使用者&#xff0c;让访问速度加快&#xff0c;从而提升系统性能。工作机制大概是先从缓存中加载数据&#xff0c;如果没有&#xff0c;再从慢速设备(eg:数据库)中加载数据并同步到缓存中。 所谓多级缓存&#xff0c;是指在整个系统架…...

机器学习笔记:李宏毅chatgpt 大模型 大资料

1 大模型 1.1 大模型的顿悟时刻 Emergent Abilities of Large Language Models&#xff0c;Transactions on Machine Learning Research 2022 模型的效果不是随着模型参数量变多而慢慢变好&#xff0c;而是在某一个瞬间&#xff0c;模型“顿悟”了 这边举的一个例子是&#…...

2023年中国智慧公安行业发展现况及发展趋势分析:数据化建设的覆盖范围不断扩大[图]

智慧公安基于互联网、物联网、云计算、智能引擎、视频技术、数据挖掘、知识管理为技术支撑&#xff0c;公安信息化为核心&#xff0c;通过互联互通、物联化、智能方式促进公安系统各功能模块的高度集成、协同作战实现警务信息化“强度整合、高度共享、深度应用”警察发展的新概…...

Apache Dubbo概述

一、课程目标 1. 【了解】软件架构的演进过程 2. 【理解】什么是RPC 3. 【掌握】Dubbo架构 4. 【理解】注册中心Zookeeper 5. 【掌握】Zookeeper的安装和使用 6. 【掌握】Dubbo入门程序 7. 【掌握】Dubbo管理控制台的安装和使用 8. 【理解】Dubbo配置二、分布式RPC框架Apache …...

React UI组件库

1 流行的开源React UI组件库 1 material-ui(国外) 官网: Material UI: React components based on Material Design github: GitHub - mui/material-ui: MUI Core: Ready-to-use foundational React components, free forever. It includes Material UI, which implements Go…...

计算机科学的伟大变革:从机械计算到人工智能

摘要 计算机科学作为一门学科&#xff0c;经历了几十年的发展和演变。本论文旨在探讨计算机科学领域的伟大变革&#xff0c;从最早的机械计算设备到如今的人工智能系统。通过回顾历史、分析技术进步以及展望未来&#xff0c;我们可以清晰地看到计算机科学如何塑造了现代社会&a…...

微服务详解

微服务 什么是微服务&#xff1f; 微&#xff1a;单个服务的设计&#xff0c;所有参与人从设计、开发、测试、运维所有人加起来只需要两个披萨就够了 服务&#xff1a;一定要区别于系统&#xff0c;服务一个或者一组相对较小且独立的功能单元&#xff0c;是用户可以感知的最…...

Python爬虫的requests(学习于b站尚硅谷)

目录 一、requests  1. requests的基本使用  &#xff08;1&#xff09;文档  &#xff08;2&#xff09;安装  &#xff08;3&#xff09;响应response的属性以及类型  &#xff08;4&#xff09;代码演示 2.requests之get请求  3. requests之post请求  &#x…...

PHP最简单自定义自己的框架view使用引入smarty(8)--自定义的框架完成

1、实现效果。引入smarty&#xff0c; 实现assign和 display 2、下载smarty&#xff0c;创建缓存目录cache和扩展extend 点击下面查看具体下载使用&#xff0c;下载改名后放到extend PHP之Smarty使用以及框架display和assign原理_PHP隔壁老王邻居的博客-CSDN博客 3、当前控…...

字符串的常用操作

1.拼接字符串 使用运算符""可以对多个字符串进行拼接将几个字符串拼成一个字符串。 2.计算字符串的长度 使用len()函数计算字符串的长度 len(string) 其中string用于指定要进行长度统计的字符串。 3.截取字符串 由于字符串也属于序列&#xff0c;所以要截取字…...

开启想象翅膀:轻松实现文本生成模型的创作应用,支持LLaMA、ChatGLM、UDA、GPT2、Seq2Seq、BART、T5、SongNet等模型,开箱即用

开启想象翅膀&#xff1a;轻松实现文本生成模型的创作应用&#xff0c;支持LLaMA、ChatGLM、UDA、GPT2、Seq2Seq、BART、T5、SongNet等模型&#xff0c;开箱即用 TextGen: Implementation of Text Generation models 1.介绍 TextGen实现了多种文本生成模型&#xff0c;包括&a…...

文心一言 VS 讯飞星火 VS chatgpt (74)-- 算法导论7.2 2题

二、如果用go语言&#xff0c;当数组A的所有元素都具有相同值时&#xff0c;QUICKSORT的时间复杂度是什么? 文心一言&#xff1a; 在Go语言中&#xff0c;对一个所有元素都相等的数组进行快速排序&#xff08;QuickSort&#xff09;的时间复杂度是O(n log n)。 快速排序是一…...

大数据第二阶段测试

大数据第二阶段测试 一、简答题 Flume 采集使用上下游的好处是什么&#xff1f; 参考答案一 -上游和下游可以实现解耦&#xff0c;上游不需要关心下游的处理逻辑&#xff0c;下游不需要关心上游的数据源。 -上游和下游可以并行处理&#xff0c;提高整体处理效率。 -可以实现…...

06 为什么需要多线程;多线程的优缺点;程序 进程 线程之间的关系;进程和线程之间的区别

为什么需要多线程 CPU、内存、IO之间的性能差异巨大多核心CPU的发展线程的本质是增加一个可以执行代码工人 多线程的优点 多个执行流&#xff0c;并行执行。&#xff08;多个工人&#xff0c;干不一样的活&#xff09; 多线程的缺点 上下文切换慢&#xff0c;切换上下文典型值…...

多元多步多站点时间序列预测在空气质量监测中的应用

1. 多元多步多站点时间序列预测问题概述时间序列预测在实际应用中面临着诸多挑战&#xff0c;这些挑战源于问题的复杂性特征&#xff1a;多输入变量、需要预测多个时间步长&#xff0c;以及需要对多个物理站点进行相同类型的预测。这类问题在空气质量预测、交通流量预测、电力负…...

从零部署Evo AI:开源智能体平台架构解析与生产实践指南

1. 项目概述&#xff1a;一个开源的AI智能体构建与管理平台如果你正在寻找一个能够将不同的大语言模型、工具和协议整合在一起&#xff0c;用来构建和编排复杂AI智能体的平台&#xff0c;那么Evo AI值得你花时间深入了解。我最近花了几周时间&#xff0c;从零开始部署、配置并深…...

新手避坑指南:是德N5171B信号源从开机到输出第一个信号的完整流程

新手避坑指南&#xff1a;是德N5171B信号源从开机到输出第一个信号的完整流程 第一次接触是德科技N5171B信号源时&#xff0c;面对密密麻麻的按键和复杂的菜单系统&#xff0c;即使是经验丰富的工程师也可能感到无从下手。这台价值数十万元的射频仪器&#xff0c;功能强大但操作…...

算法训练营第10天(补)|26. 删除有序数组中的重复项

题目链接&#xff1a; https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 视频链接&#xff1a; https://www.bilibili.com/video/BV1fc2FByE4f/ 我的代码&#xff1a; https://leetcode.cn/problems/remove-duplicates-from-sorted-array/submissions/72…...

保姆级教程:在Ubuntu系统的AIxBoard上,用CODESYS V3.5 SP17配置软PLC,并打通Python(OpenVINO/YOLOv5)的共享内存通信

边缘智能控制实战&#xff1a;基于AIxBoard与CODESYS的软PLC-Python协同开发指南 当工业控制遇上人工智能&#xff0c;传统PLC的封闭性与现代AI算法的开放性如何实现无缝对接&#xff1f;本文将手把手带您完成从零搭建一个支持机器视觉的智能控制系统。不同于简单的理论概述&am…...

DeepEar:基于多智能体协作的金融信息自动化研究框架实践

1. 项目概述&#xff1a;从噪音中捕捉信号&#xff0c;一个量化研究者的新工具在信息爆炸的时代&#xff0c;金融市场的噪音从未如此刺耳。每天&#xff0c;海量的新闻、社交媒体讨论、研报和公告如潮水般涌来&#xff0c;对于分析师和投资者而言&#xff0c;核心挑战不再是信息…...

IntelliJ IDEA + SonarLint 插件:如何为团队项目定制专属的代码质量规则?

IntelliJ IDEA SonarLint 插件&#xff1a;团队代码质量治理的工程化实践 当十几个开发者同时向同一个代码库提交变更时&#xff0c;如何确保每个人都能遵循统一的代码卫生标准&#xff1f;这个问题困扰着许多技术团队。作为Tech Lead&#xff0c;我曾见证过因代码规范不一致导…...

AI Agent Harness Engineering 多模态能力构建:文本、图像、语音的融合应用

AI Agent Harness Engineering 多模态能力构建:文本、图像、语音的融合应用 本文面向有一定AI Agent开发基础的工程师,从零到一讲解如何通过Harness架构标准化接入多模态能力,解决传统Agent模态碎片化、上下文割裂、扩展困难的核心痛点,最终实现可生产级别的多模态智能体。…...

MySQL怎样在触发器中引用新旧数据行_NEW与OLD关键字详解

MySQL触发器中通过NEW和OLD获取字段值&#xff1a;INSERT只有NEW&#xff0c;DELETE只有OLD&#xff0c;UPDATE两者都有&#xff1b;NEW在BEFORE中可修改&#xff0c;OLD始终只读&#xff1b;注意大小写、反引号包裹特殊列名及跨库操作限制。触发器里怎么拿到修改前后的字段值M…...

别再死记硬背了!用Postman玩转阿里云市场API,5分钟搞定身份认证(AppCode/AppKey实战)

用Postman实战阿里云API&#xff1a;从零掌握身份认证核心技巧 每次面对API文档里密密麻麻的认证参数&#xff0c;你是不是也感到一阵眩晕&#xff1f;AppCode、AppKey、AppSecret这些名词看起来相似却又各司其职&#xff0c;传统死记硬背的方式不仅效率低下&#xff0c;更难以…...