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

news2024/11/16 20:39:58

目录

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

一、为什么要使用OkHhttp?

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

fun sendGetRequest(urlString: String): String? {
        var response: String? = null
        var startTime = System.currentTimeMillis() // 记录开始时间

        try {
            val url = URL(urlString)
            val connection = url.openConnection() as HttpURLConnection

            // 设置请求方法为GET
            connection.requestMethod = "GET"

            // 连接服务器
            val responseCode = connection.responseCode
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 读取响应内容
                val inputStream = connection.inputStream
                val 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 - startTime
                callback(null, duration) // 请求失败时回调,传递null作为响应体和持续时间
                e.printStackTrace()
            }

            override fun onResponse(call: Call, response: Response) {
                val endTime = System.currentTimeMillis()
                val duration = endTime - startTime

                if (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请求的发送和响应的接收过程,是实际与服务器进行交互的拦截器。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【银河麒麟高级服务器操作系统实例】tcp_mem分析处理全过程内核参数调优参考

了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://documentkylinos.cn 现象描述 系统中出现大量的TCP: out of memory…

Mina protocol - 体验教程

Mina protocol - 体验教程 一、零知识证明( Zero Knowledge Proof )1、零知识证明(ZKP)的基本流程工作流程: 2、zkApp 的优势:3、zkApp 每个方法的编译过程: 二、搭建第一个zkapp先决条件1、下载或者更新 zkApp CLI​2…

基于Springboot美食推荐小程序的设计与实现(源码+数据库+文档)

一.项目介绍 pc端: 支持用户、餐厅老板注册 支持管理员、餐厅老板登录 管理员: 管理员模块维护、 餐厅管理模块维护、 用户管理模块维护、 商品管…

Qt:NULL与nullptr的区别(手写nullptr)

前言 发现还是有人不知道NULL 与nullptr的区别,故写此文章。 正文 对于NULL 先看NULL的源码 我们可以看出这段代码是一个典型的预处理器宏定义块,用于处理 NULL 宏的定义。 先看开头 #if defined (_STDDEF_H) || defined (__need_NULL)这行代码检…

git报错,error: bad signature 0x00000000fatal: index file corrupt

报错 git -c diff.mnemonicprefixfalse -c core.quotepathfalse --no-optional-locks checkout daily --progress error: bad signature 0x00000000 fatal: index file corrupt 原因 git 仓库中索引文损坏 处理 1.该备份的先备份 2.删除索引并重置 rm -f .git/index git r…

医学数据分析实训 项目五 分类分析--乳腺癌数据分析与诊断

文章目录 项目六:分类分析实践目的实践平台实践内容(一)数据理解及准备(二)模型建立、预测及优化任务一:使用 KNN算法进行分类预测任务二:使用贝叶斯分类算法进行分类预测任务三:使用…

Linux基础3-基础工具4(git,冯诺依曼计算机体系结构)

上篇文章:Linux基础3-基础工具3(make,makefile,gdb详解)-CSDN博客 本章重点: 1. git简易使用 2. 冯诺依曼计算机体系结构介绍 一. git使用 1.1 什么是git? git是用于管理代码版本的一种工具,我们在如GitHub&#xf…

C++ | (二)类与对象(上)

燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的假期为什么一去不复返呢? 目录 一、初识类 1.1 类的定义 1.2 C中…

面试真题-TCP的三次握手

TCP的基础知识 TCP头部 面试题:TCP的头部是多大? TCP(传输控制协议)的头部通常是固定的20个字节长,但是根据TCP选项(Options)的不同,这个长度可以扩展。TCP头部包含了许多关键的字…

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生

depcheck 检查项目中依赖的使用情况 避免幽灵依赖的产生 什么是幽灵依赖 (幻影依赖) 形成原因 幽灵依赖是指node_modules中存在 而package.json中没有声明过的依赖 但却能够在项目的依赖树中找到并使用的模块 Node.js 的模块解析规则: Node.js 采用了一种非传统的模…

C++速通LeetCode简单第20题-多数元素

方法一&#xff1a;暴力解法&#xff0c;放multiset中排序&#xff0c;然后依次count统计&#xff0c;不满足条件的值erase清除。 class Solution { public:int majorityElement(vector<int>& nums) {int ans 0;multiset<int> s;for(int i 0;i < nums.s…

「iOS」viewController的生命周期

iOS学习 ViewController生命周期有关方法案例注意 ViewController生命周期有关方法 init - 初始化程序&#xff1b;loadView - 在UIViewController对象的view被访问且为空的时候调用&#xff1b;viewDidLoad - 视图加载完成后调用&#xff1b;viewWillAppear - UIViewControll…

给大模型技术从业者的建议,入门转行必看!!

01—大模型技术学习建议‍‍‍ 这个关于学习大模型技术的建议&#xff0c;也可以说是一个学习技术的方法论。 首先大家要明白一点——(任何)技术都是一个更偏向于实践的东西&#xff0c;具体来说就是学习技术实践要大于理论&#xff0c;要以实践为主理论为辅&#xff0c;而不…

换个手机IP地址是不是不一样?

在当今这个信息爆炸的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。而IP地址&#xff0c;作为手机连接网络的桥梁&#xff0c;也时常引起我们的关注。你是否曾经好奇&#xff0c;换个手机&#xff0c;IP地址会不会也跟着变呢&#xff1f;本文将深入探讨这个问题&a…

Android15之编译Cuttlefish模拟器(二百三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

直流斩波电路

目录 1. 降压斩波电路&#xff08;Buck Converter&#xff09; 2. 升压斩波电路&#xff08;Boost Converter&#xff09; 3. 升降压斩波电路&#xff08;Buck-Boost Converter&#xff09; 4. Cuk斩波电路&#xff08;Cuk Converter&#xff09; 直流斩波电路是一种将直流…

Unity3D下如何播放RTSP流?

技术背景 在Unity3D中直接播放RTSP&#xff08;Real Time Streaming Protocol&#xff09;流并不直接支持&#xff0c;因为Unity的内置多媒体组件&#xff08;如AudioSource和VideoPlayer&#xff09;主要设计用于处理本地文件或HTTP流&#xff0c;而不直接支持RTSP。所以&…

上海人工智能实验室开源视频生成模型Vchitect 2.0 可生成20秒高清视频

上海人工智能实验室日前推出的Vchitect2.0视频生成模型正在悄然改变视频创作的游戏规则。这款尖端AI工具不仅简化了视频制作流程&#xff0c;还为创作者提供了前所未有的灵活性和高质量输出。 Vchitect2.0的核心优势在于其强大的生成能力和高度的可定制性。用户只需输入文字描…

用Matlab求解绘制2D散点(x y)数据的最小外接圆、沿轴外接矩形

用Matlab求解绘制2D散点&#xff08;x y&#xff09;数据的最小外接圆、沿轴外接矩形 0 引言1 原理概述即代码实现1.1 最小外接圆1.2 沿轴外接矩形 2 完整代码3 结语 0 引言 本篇简单介绍下散点数据最小外接圆、沿轴外接矩形的简单原理和matlab实现过程。 1 原理概述即代码实现…

C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)

拓扑排序算法的实现还是比较简单的&#xff0c;我们需要用到一个顺序栈辅助&#xff0c;采用邻接表进行存储&#xff0c;顶点结点存储入度、顶点信息、指向邻接结点的指针&#xff0c;算法过程是&#xff1a;我们先将入度为0的顶点入栈&#xff0c;然后弹出栈顶结点&#xff0c…