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

Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发

目录

1. 为什么要使用OkHhttp?
2. Http请求的过程是怎么样的?
3. 分发器是什么?
4. 拦截器是什么?

一、为什么要使用OkHhttp?

在不使用OkHhttp之前,我们都是在使用什么?使用HttpURLConnection,那么我们看看HttpURLConnection发起一次请求,两次请求要花多长时间,而OkHttp花多长时间。HttpURLConnection会比okhttp花更多的时间。
(1)HttpURLConnection

fun sendGetRequest(urlString: String): String? {var response: String? = nullvar startTime = System.currentTimeMillis() // 记录开始时间try {val url = URL(urlString)val connection = url.openConnection() as HttpURLConnection// 设置请求方法为GETconnection.requestMethod = "GET"// 连接服务器val responseCode = connection.responseCodeif (responseCode == HttpURLConnection.HTTP_OK) {// 读取响应内容val inputStream = connection.inputStreamval reader = BufferedReader(InputStreamReader(inputStream))val responseBuilder = StringBuilder()val readLine = reader.readLine()println("GET请求成功:$readLine")response = responseBuilder.toString()} else {// 处理错误情况println("GET请求失败: HTTP错误码 $responseCode")}} catch (e: Exception) {e.printStackTrace()println("GET请求失败:  "+e)}var endTime = System.currentTimeMillis() // 记录结束时间val duration = endTime - startTime // 计算总时间println("请求总时间: $duration 毫秒")return response}

在这里插入图片描述
(2)OkHttp

  // 发送GET请求并记录时间的函数fun sendGetRequestWithTime(url: String, callback: (String?, Long) -> Unit) {val client = OkHttpClient()val request = Request.Builder().url(url).build()var startTime = System.currentTimeMillis() // 记录请求开始时间client.newCall(request).enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {val endTime = System.currentTimeMillis()val duration = endTime - startTimecallback(null, duration) // 请求失败时回调,传递null作为响应体和持续时间e.printStackTrace()}override fun onResponse(call: Call, response: Response) {val endTime = System.currentTimeMillis()val duration = endTime - startTimeif (response.isSuccessful) {// 读取响应体response.body?.string()?.let { responseBody ->callback(responseBody, duration) // 请求成功时回调,传递响应体和持续时间}} else {// 处理HTTP错误callback(null, duration) // 传递null作为响应体和持续时间println("HTTP请求失败: ${response.code}")}}})}
 var btnOkhttp: Button = findViewById(R.id.btn_okhttp);btnOkhttp.setOnClickListener {CoroutineScope(Dispatchers.IO).launch {sendGetRequestWithTime("xxxxx") { responseBody, duration ->if (responseBody != null) {println("响应内容: $responseBody")println("请求总时间: $duration 毫秒")} else {println("请求失败或未获取到响应内容")}}}}

在这里插入图片描述

可以看到,时间不相上下,为什么呢?不是说OkHttp更快?其实,从Android4.4开始HttpURLConnection的底层实现采用的是okHttp。

所以如果是使用android4.4以前的版本,就会发现HttpURLConnection比okhttp慢,并且每次请求都是这么慢,比如每次都需要两秒的时间,两次请求,就花了四秒,而Okhttp就不一样,第一次请求建立会花些时间,但随后的请求就是毫秒级,这究竟是为什么?

那么?耗时的地方究竟是在哪里吗?

在这里插入图片描述

我们先了解一下Http的请求过程是怎么样的。

二、Http请求的过程是怎么样的

结论:因为他支持一个主机一个长连接,允许对同一主机的所有请求共享一个套接字;

首先,Http协议用来规范我们的格式,负责数据的收发和管理,那么用什么传输呢?就需要TCP来拿建立连接传输,而这个过程需要三次握手,TCP要传输,也要知道往哪里来传递,所以借助IP协议来确认传输的目的地。而TCP/IP的连接,在程序里面我们就可以使用Socket来建立。建立后,要完成Http的通讯,那么我们就需要建立Http的报文,使用Socket的流来发送数据。

在这整一个过程中,如果说一次请求要花两秒,那么1.8秒都花在了建立三次握手这里,而数据传输0.2秒

所以OkHttp就对这一个过程做了优化,对域名建立长连接,数据传输共享一个套接字,后续数据传输的时候,就不需要建立三次握手了
并且Okhttp提供默认的请求压缩格式Gzip,能够将数据进行压缩,这样就可以提高我们传输的效率。

Socket和TCP/IP有什么关系?Socket是他们的抽象,也可以说是上层实现,封装。

三、分发器是什么?

通过上面的代码,我们可以看到,OkHttp请求过程中最少只需要接触OkHttpClient、 Request、 Call、Response

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

那么分发器是做什么的?主要是负责我们请求任务什么时候执行,什么时候被调用。注意他只是做请求任务的调配和分发,还没开始发送请求,因为那是拦截器做的事情:

1.请求队列:分发器内部维护了多个请求队列,包括等待执行的异步任务队列(readyAsyncCalls)、正在运行的异步任务队列(runningAsyncCalls)以及运行中的同步任务队列(runningSyncCalls)。这些队列共同协作,确保网络请求的有序进行。
2. 线程池:分发器还负责维护一个线程池(ThreadPoolExecutor),用于执行异步网络请求。线程池可以避免频繁地创建和销毁线程,从而提高程序的性能和效率。

3.1 源码分析:请求队列

可以看到我们会传递request进去,Request对象用来表示你想要进行的HTTP请求(包括URL、请求头、请求体等)
在这里插入图片描述
Call,就是一个任务。这个Call对象封装了实际的请求操作,并提供了方法来执行请求、取消请求以及监听请求结果。调用enqueue方法,这个方法会异步地执行HTTP请求。异步,那么就是线程。
在这里插入图片描述在这里插入图片描述
enqueue方法只能调用一次,否则就会报错。
在这里插入图片描述
接下来我们看看最后一句代码:client.dispatcher.enqueue(AsyncCall(responseCallback)),Dispatcher类,我们可以看到里面有三个队列:等待执行的异步任务队列(readyAsyncCalls)、正在运行的异步任务队列(runningAsyncCalls)以及运行中的同步任务队列(runningSyncCalls),我们调用的是enqueue,所以会往异步队列里面增加:

在这里插入图片描述
那么,什么时候往执行队列里面增加,什么时候完准备队列里面增加呢?正在执行的异步个数,大于64,则放到准备队列。同个域名的请求最大个数不大于5个,总要有一个上限,不能无限制。
在这里插入图片描述
在这里插入图片描述
将符合条件的调用从readyAsyncCalls提升到runningAsyncCalls
在这里插入图片描述
请求执行完成后,就会继续取下一个
在这里插入图片描述
在这里插入图片描述

3.2 源码分析:线程池

在这里插入图片描述
为什么他要这样定义?这些参数的作用是什么?

核心线程数:定义为0,比如你设置成了x个,那么就会一直维护x个线程。
最大线程数:同时执行的最大线程数量
空闲时间:空闲了60s的超过核心线程数的线程会被回收
任务队列:SynchronousQueue

为什么使用SynchronousQueue,不使用ArrayBlockingQueue?如果使用ArrayBlockingQueue你需要定义究竟有多少个任务,所以不方便。而SynchronousQueue是一个没有容量的queue,没有长度,只要你提交一个请求,他就会失败,那么失败以后,就会看是否达到了最大线程数,如果没有,就立马创建一个继续执行。所以他是一个无需等待,最大并发的线程池。高并发。

总结:

  1. 当一个任务通过execute(Runnable)方法添加到线程池时:线程数量小于corePoolSize,新建线程(核心)来处理被添加的任务;
  2. 线程数量大于等于 corePoolSize,新任务被添加到等待队列,添加失败:线程数量小于maximumPoolSize,新建线程执行新任务;线程数量等于maximumPoolSize,使用RejectedExecutionHandler拒绝策略。
    在这里插入图片描述

四、拦截器

OkHttp的拦截器(Interceptor)是OkHttp库中的一个核心组件,**负责完成整个请求过程。**它提供了对HTTP请求和响应进行全面控制的能力。拦截器允许开发者在HTTP请求和响应的各个阶段进行自定义处理,比如日志记录、请求重试、响应缓存等。

OkHttp拦截器的执行流程以及拦截器有哪些?

在这里插入图片描述这些拦截器做了些什么样的事情?简单来说,就是打开Socket,然后处理Socket的数据封装成response返回给我们。

4.1 重试和重定向拦截器(RetryAndFollowUpInterceptor):负责重试和重定向

    第一个接触到请求,最后接触到响应。负责判断是否需要重新发起整个请求,包括处理异常和重定向。在请求阶段发生RouteException或IOException时,会根据一定的逻辑判断是否重新发起请求。重定向的判断则基于响应码,如果需要重定向,则会构造一个新的请求并继续处理。

4.2 桥接拦截器(BridgeInterceptor):负责补全请求头等报文

    补全请求,包括添加必要的请求头信息。对响应进行额外处理,如处理gzip压缩的响应数据,以及保存和读取cookie。桥接拦截器在应用层和网络层之间起到了桥梁的作用,确保请求和响应能够正确地被处理和传递。

4.3 缓存拦截器(CacheInterceptor):

    请求前查询缓存,如果缓存中存在有效响应,则直接返回缓存响应,避免不必要的网络请求。如果网络请求成功,则根据缓存策略判断是否需要更新缓存。缓存拦截器通过管理缓存数据,提高了应用的响应速度和性能。

4.4 连接拦截器(ConnectInterceptor):长连接

    负责与服务器完成TCP连接(Socket)。内部维护一个连接池,负责连接复用、创建连接、释放连接等。连接拦截器还负责封装请求数据和解析响应数据,如HTTP报文的封装和解析。

4.5 请求服务拦截器(CallServerInterceptor):利用socket发出请求,读取响应

    也可以称为“读写拦截器”,因为它主要负责与服务器进行通信,包括发送请求数据和接收响应数据。封装了HTTP请求的发送和响应的接收过程,是实际与服务器进行交互的拦截器。

好了,这篇文章就先介绍到这里。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Python 集成快递物流 API 助力订单追踪:轻松实现物流可视化
  • 如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)
  • 面试真题-TCP的三次握手
  • 线性基速通
  • 【STM32】独立看门狗(IWDG)原理详解及编程实践(上)
  • OpenCV-模板匹配多个目标
  • 【Java】网络编程:TCP_IP协议详解(IP协议数据报文及如何解决IPv4不够的状况)
  • React学习day07-ReactRouter-抽象路由模块、路由导航、路由导航传参、嵌套路由、默认二级路由的设置、两种路由模式
  • Java多线程面试精讲:源于技术书籍的深度解读
  • Python中 BeautifulSoup和Selenium 定位元素和获取元素值的方法
  • 基于Jeecgboot3.6.3的flowable流程增加任务节点字段的控制(一)
  • 代理导致的git错误
  • 【STM32 Blue Pill编程】-定时器PWM模式
  • 系统架构设计师:软件架构的演化和维护
  • Qt自动打开文件夹并高亮文件
  • @jsonView过滤属性
  • 【comparator, comparable】小总结
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 08.Android之View事件问题
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • css布局,左右固定中间自适应实现
  • echarts的各种常用效果展示
  • iOS | NSProxy
  • Laravel 实践之路: 数据库迁移与数据填充
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • MySQL主从复制读写分离及奇怪的问题
  • NSTimer学习笔记
  • PHP的Ev教程三(Periodic watcher)
  • Protobuf3语言指南
  • python学习笔记 - ThreadLocal
  • Transformer-XL: Unleashing the Potential of Attention Models
  • VuePress 静态网站生成
  • yii2权限控制rbac之rule详细讲解
  • 关于 Cirru Editor 存储格式
  • 利用jquery编写加法运算验证码
  • 每天10道Java面试题,跟我走,offer有!
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 我感觉这是史上最牛的防sql注入方法类
  • 职业生涯 一个六年开发经验的女程序员的心声。
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​字​节​一​面​
  • # 数论-逆元
  • ## 基础知识
  • #{}和${}的区别?
  • #Ubuntu(修改root信息)
  • #数学建模# 线性规划问题的Matlab求解
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (4)事件处理——(7)简单事件(Simple events)
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (a /b)*c的值
  • (done) 两个矩阵 “相似” 是什么意思?
  • (PySpark)RDD实验实战——取最大数出现的次数
  • (回溯) LeetCode 46. 全排列
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (三)Kafka 监控之 Streams 监控(Streams Monitoring)和其他