首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >重识OkHttp:从深入了解到源码分析

重识OkHttp:从深入了解到源码分析

作者头像
陈宇明
发布于 2020-12-15 03:28:12
发布于 2020-12-15 03:28:12
1.2K0
举报
文章被收录于专栏:设计模式设计模式

作者博客

http://www.jianshu.com/u/16925b46816d

前言

本文的分析基于OkHttp3.4,不展示完整的代码示例,具体可以看这个官方例子或者项目中的例子。

https://github.com/square/okhttp/wiki/Recipes

OkHttp作为square公司出品的一个网络请求框架,应该算是目前Android端最火爆的网络框架了。我公司目前的项目中采用的都是Rxjava结合Retrofit进行网络请求的处理,对于底层真正实现网络请求的OkHttp关注的不是很多。最近探究了一下OkHttp的源码,对OkHttp的使用有了一些新的认识,在此做一下总结。

目录

  1. 使用篇
    1. OkHttp的优点
    2. 网络处理3要素
      1. 请求Request
        1. 表单FormBody
        2. 分块MultipartBody
      2. 客户端OkHttpClient
    3. 同步请求和异步请求
    4. 其他
      1. 配置响应缓存
      2. 取消请求
      3. Response读取响应结果
    5. 总结
  2. 分析篇
    1. 客户端完整的请求
    2. 探究源码
      1. Call的实现类RealCall
      2. RealCall的enqueue( Callback callBack)
      3. Dispatcher的enqueue
      4. getResponseWithInterceptorChain()
    3. 同步请求
    4. 总结

1

使用篇

1.OkHttp的优点

OkHttp作为当前Android端最火热的网络请求框架,必然有很多的优点。

  • 支持HTTP / 2协议,允许连接到同一个主机地址的所有请求共享Socket。这必然会提高请求效率。
  • 在HTTP / 2协议不可用的情况下,通过连接池减少请求的延迟。
  • GZip透明压缩减少传输的数据包大小。
  • 响应缓存,避免同一个重复的网络请求。

2.网络处理3要素

对于客户端来讲,我们关注的就是把正确的请求发送到服务端并拿到结果来进行处理。在OkHttp中,我认为可以分为3个部分:

  • 请求类封装客户端发送的请求,包括请求的url,请求方法(主要是GET和POST方法),请求头标题以及请求体requestBody;
  • 响应类封装了服务器响应的数据,包括代码,消息,主体,头等。
  • OkHttpClient负责发送请求请求并通过同步或者异步的方式返回服务器的响应响应,就好比是一个浏览器。

OkHttp中通过建造者模式来构建OkHttpClient,请求和响应对于客户端来讲,我们不需要过多关注响应是如何构建的,因为这个是OkHttp对响应结果进行了封装处理。我们只关注请求请求和客户端OkHttpClient如何构建即可。

2.1请求Request

请求采用建模者模式来配置url,请求方法method,header,tag和cacheControl。

  • 设置url。可以是String类型,URL类型和HttpUrl类型。最终都是用到HttpUrl类型。
  • 设置方法,包含get,post方法等。默认的是get方法.post方法要传RequestBody,类似的还有delete,put,patch。
  • 设置头,方法有addHeader(String name,String value),removeHeader(String name),header(String name,String value),headers(Headers headers).headers(Headers headers)调用之后其它的头都会被移除,只添加这一个标题。而头(String name,String value)方法调用之后,其它与这个名称同名的标题都会被移除,只保留这一个标题。
  • 设置标签,设置标签可以用来取消这一请求。如果未指定标签或者标签为null,那么这个请求本身就会当做是一个标签用来被取消请求。
  • 设置cacheControl,这个是设置到请求头中。用来替换其它名称是“Cache-Control”的头。如果cacheControl是空的话就会移除请求头中名是“Cache-Control”的头。

OkHttp采用POST方法向服务器发送一个请求体,在OkHttp中这个请求体是RequestBody。这个请求体可以是:

  • 字符串类型
  • 流流类型
  • 文件类型
  • 表单形式的键值类型
  • 类似Html文件上传表单的复杂请求体类型(多块请求)

RequestBody有几个静态方法用于创建不同类型的请求体:

最终都是相当于重写了RequestBody的两个抽象方法来写入流,如果传递流类型的参数,只要重写这两个抽象方法即可。

例如,我们提交一个String:

提交File:

提交流:

对于提交表单和分块请求,OkHttp提供了两个RequestBody的子类,FormBodyMultipartBody

2.1.1 表单FormBody

FormBody也是采用建造者模式, 这个很简单,添加key-value形式的键值对即可。 添加键值对有两个方法:

例如:

2.1.2 分块MultipartBody

MultipartBody也是采用建造者模式,MultipartBody.Builder可以构建兼容Html文件上传表单的复杂请求体。每一部分的多块请求体都是它自身的请求体,并且可以定义它自己的请求头。如果存在的话,这些请求头用来描述这部分的请求体。例如Content-Disposition、Content-Length 和 Content-Type如果可用就会被自动添加到头。

MIME类型有:

有几个主要的方法:

例如提交一个图片文件:

2.2 客户端OkHttpClient

OkHttpClient采用建造者模式,通过Builder可以配置连接超时时间、读写时间,是否缓存、是否重连,还可以设置各种拦截器interceptor等。 建议在一个App中,OkHttpClient保持一个实例。一个OkHttpClient支持一定数量的并发,请求同一个主机最大并发是5,所有的并发最大是64。这个与OkHttp中的调度器Dispatcher有关,可以设置并发数。本文不对Dispatcher进行讨论。

一个例子:

OkHttpClient支持单独配置,例如原来设置不同的请求时间,可以通过OkHttpClient的newBuilder()方法来重新构造一个OkHttpClient。例如:

3.同步请求和异步请求

上面已经讲了如何创建Request和OkHttpClient,剩下的就是发送请求并得到服务器的响应了。OkHttp发送请求可分为同步和异步。OkHttpClient首先通过Request构建一个Call,通过这个Call去执行同步或者异步请求。

同步方式,调用Call的execute()方法,返回Response,会阻塞当前线程:

异步方式,调用Call的enqueue(CallBack callBack)方法,会在另一个线程中返回结果。

4.其他

4.1 配置响应缓存

为了缓存响应,需要一个可读写并且设置大小Size的缓存目录。缓存目录需要私有,其它不信任的应用不能访问这个文件。 如果同时有多个缓存访问同一个缓存目录会报错。所以最好只在App中初始化一次OkHttpClient,给这个实例配置缓存,在整个App生命周期内都用这一个缓存。否则几个缓存会相互影响,导致缓存出错,引起程序崩溃。 响应缓存采用Http头来配置,你可以添加这样的请求头Cache-Control: max-stale=3600。 max-age指的是客户端可以接收生存期不大于指定时间(以秒为单位)的响应。 为了防止响应使用缓存,可以用CacheControl.FORCE_NETWORK。为了防止使用网络,采用 CacheControl.FORCE_CACHE

注意:如果使用FORCE_CACHE禁止使用网络,而响应又没有缓存存在,OkHttp会报504 Unsatisfiable Request 响应错误。

4.2 取消请求

调用Call.cancel()方法可以立即取消一个网络请求。如果当前线程正在写request或者读response会报IO异常。如果不再需要网络请求,采用这种方法是比较方便的。例如在App中返回了上一页。无论是同步还是异步的请求都可以被取消。

4.3 Response读取响应结果

可以通过Response的code来判断请求是否成功,如果服务器返回的有数据,可以通过Response的body得到一个ResponseBody读取。 如果采用ResponseBody的string()方法会一次性把数据读取到内存中,如果数据超过1MB可能会报内存溢出,所以对于超过1MB的数据,建议采用流的方式去读取,如ResponseBody的byteStream()方法。

需要说明的是:

  • 如果ResponseBody的内容不读取的话,不会触发IO流的读取操作
  • 内容读取之后,这个body需要关闭。

5 总结

OkHttp中的很多类都用到了建造者模式,可以根据需要灵活配置。采用建造者模式的有:

  • OkHttpClient.Builder
  • Request.Builder
  • FormBody.Builder
  • MultipartBody.Builder
  • Response.Builder

如果单独使用OkHttp进行网络请求,通常需要开发者自己再封装一下,如果不想重复造轮子,Github上面的有一些优秀开源库可以拿来使用(本文只列出star较多的几个):

  • hongyangAndroid/okhttputils(曾经在项目中用过)
  • jeasonlzy/okhttp-OkGo
  • yanzhenjie/NoHttp

2

分析篇

1.客户端完整的请求

OkHttp发送一个请求需要4步:

  • 构建OkHttpClient
  • 构建Request
  • 创建一个Call
  • 执行Call的同步或者异步方法,处理响应。

我们只以一个简单的异步get请求来举例:

Request和OkHttpClient都是我们自己创建的,不再讨论了。就从这个Call来展开讨论。

2.探究源码

2.1 Call的实现类RealCall

Call是一个准备执行的请求,它是一个接口。含有一个内部接口Factory 用于生成Call。

OkHttpClient实现了Call.Factory接口,所以有一个newCall方法,这个方法中干了这么个事:

可以看到,返回了一个RealCall,这个RealCall是OkHttp中Call的唯一实现类。说明我们执行请求,是通过RealCall发出的。 在RealCall的构造方法中,我们还创建了一个拦截器RetryAndFollowUpInterceptor,通过名字我们可以猜测一下这个拦截器的作用是重试和跟进,这个负责是否断线重连和重定向,可以看到这个拦截器跟OkHttpClient有关联,我们可以在配置OkHttpClient的时候配置断线重连等,默认的都是true。

在OkHttp中以Real为前缀的类,都是真正干活的类

2.2 RealCall的enqueue( Callback callBack)方法

OkHttpClient的newCall方法只是创建了一个RealCall,RealCall的enqueue方法传递了一个CallBack用于处理回调,那我们看看这个方法都干了些什么:

从上面可以看出,同一个Call只能执行一次,否则会报错。 client.dispatcher()返回的是与OkHttpClient绑定的一个Dispatcher。这个Dispatcher用来管理请求的调度。在使用篇我们简单的也提到过这个类。这个类主要是用来管理异步请求的调度,同步请求中虽然也参与了,但只是简单的统计正在执行的Call并在Call执行完毕之后做相应的处理。

AsyncCall是RealCall的内部类,继承了NamedRunnable,实际上也是一个Runnable实现类。这个AsyncCall 包装了Callback。它的run()方法中最终会调用它自己的execute()方法。后面我们会讲到AsyncCall的execute()方法。

RealCall的enqueue( Callback callBack)实际上最后调用了Dispatcher的enqueue(AsyncCall call)方法。

2.3 Dispatcher的enqueue方法

Dispatcher的enqueue方法是这样的:

这个方法中首先会判断当前正在执行的Call的数量以及访问同一个主机地址的Call的数量是否在限定范围内。Dispatcher默认的Call的并发数是64,同一个主机地址的并发数是5。这个并发数可以更改。 如果满足条件,就向代表当前正在执行的Call的集合中添加该Call,并且去执行它。否则就会向等待的集合中添加该Call,等待被执行。

executorService()返回的是ExecutorService对象,调用ExecutorService的execute(call)方法实际上最后调用的就是AsyncCall 的execute()方法。

在这个execute()方法中,能获得返回Response,之后做回调处理,Dispatcher也会对Call进行管理。核心的方法是getResponseWithInterceptorChain()。

2.4 核心方法getResponseWithInterceptorChain()

在这个getResponseWithInterceptorChain()方法中,有大量的Interceptors,有开发者自己定义的Interceptor也有OkHttp自己的Interceptor。这些Interceptor都存入到了ArrayList集合,我们在这里就可以大胆猜测一下这个Interceptor应该是顺序执行的。最后创建了一个RealInterceptorChain,通过调用它的proceed(request)方法开始处理原始的request,然后我们就拿到了我们想要的Response。

RealInterceptorChain是Interceptor.Chain的实现类,看这个Real前缀就知道它干的绝对是重要的事。它是一个具体的拦截器链,我们存放在List<Interceptor> interceptors集合中的拦截器之间的传递都要靠它。

它的proceed(request)最终会调用到四个参数的重载方法:

上述代码部分就是这个方法的核心,能够把Request依次传递给下一个Interceptor去处理。 拦截器Interceptor的设计真是很赞,每一个Interceptor在发送Request的时候只处理自己那一部分Request,然后通过RealInterceptorChain的带动传递给下一个Interceptor进行处理,最后一个Interceptor发送完请求得到服务器的响应Response,经过自己的处理之后返回给它之前的那个Interceptor进行处理,依次进行,最后一个处理完毕的Response返回给开发者用户。 形象一点,Interceptor就像是生产线上的工人,Request是物料,Response是产品,RealInterceptorChain是一节一节的传送带。每个工人同时负责处理自己那一部分的Request和Response,由传送带进行传递,各司其职,最后完成一件对用户来讲完美的产品。

我第一次看OkHttp3.4源码的的时候真是一脸懵逼继而叹为观止。后来看了其它文章,才知道原来这个设计模式叫作责任链模式。在Android源码设计模式解析与实战这本书中介绍了Android的事件分发处理采用的也是责任链模式。

具体到每个拦截器都是怎么处理Request和Response的,最好自己去看一下,我们就不展开讨论了。

3.同步请求

看了异步请求的调用,同步请求的分析就比较简单最终调用的是getResponseWithInterceptorChain()这个核心方法。

4.总结

本文对OkHttp一个完整的请求过程做了简单的说明,限于篇幅有些地方讲的不是很详细,有兴趣的读者可以自己去探索一下这个流程,尤其是在拦截器那一部分,掌握好了之后对我们平时灵活运用OkHttp会有很大帮助,如配置我们自定义的缓存拦截器,或者在拦截器中监听下载进度,网上也有很多文章可供参考。

参考:

OkHttp官方Wiki文档

从OKHttp框架看代码设计

拆轮子系列:拆 OkHttp

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码个蛋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Apache Doris + Paimon 快速搭建指南|Lakehouse 使用手册(二)
湖仓一体(Data Lakehouse)融合了数据仓库的高性能、实时性以及数据湖的低成本、灵活性等优势,帮助用户更加便捷地满足各种数据处理分析的需求。在过去多个版本中,Apache Doris 持续加深与数据湖的融合,已演进出一套成熟的湖仓一体解决方案。
SelectDB技术团队
2024/07/25
4640
2025 年 4 月 Apache Hudi 社区新闻
欢迎阅读由 Onehouse.ai[1] 为您带来的2025年4月版Hudi通讯!本月,我们将为您带来另一轮令人兴奋的社区更新、技术深度探讨以及展示Apache Hudi如何推动现代数据湖仓架构边界的真实案例。
ApacheHudi
2025/05/09
900
2025 年 4 月 Apache Hudi 社区新闻
老板说要降本又增效,我把Paimon搬进了Doris家,然后...
"大家看下这张监控大屏,11.11活动期间的流量洪峰正在不断涌入,业务部门急需实时数据支撑。他们既要上亿TPS的实时写入能力,又要秒级查询响应……"
一臻数据
2024/12/31
4520
老板说要降本又增效,我把Paimon搬进了Doris家,然后...
兼容Trino Connector,扩展Apache Doris数据源接入能力|Lakehouse 使用手册(四)
Apache Doris 内置支持包括 Hive、Iceberg、Hudi、Paimon、LakeSoul、JDBC 在内的多种 Catalog,并为其提供原生高性能且稳定的访问能力,以满足与数据湖的集成需求。而随着 Apache Doris 用户的增加,新的数据源连接需求也随之增加。因此,从 3.0 版本开始,Apache Doris 引入了 Trino Connector 兼容框架。
SelectDB技术团队
2024/09/06
1630
数据无界、湖仓无界,Apache Doris 湖仓一体典型场景实战指南(下篇)
在数据驱动决策的时代,湖仓一体架构以统一存储、统一计算、统一管理的创新形式,补齐了传统数据仓库和数据湖的短板,逐步成为企业大数据解决方案新的标准。
SelectDB技术团队
2025/02/21
3950
老板既要又要还要......我用Doris+Hudi把不可能变成了日常
听说过"欲速则不达"这句话吗?在大数据领域,这个道理再适用不过了。想要又快又好地分析PB级数据,光靠数据库"单打独斗"已经不够看了。正如功夫大师需要"内外兼修",现代数据架构也需要数据湖与数据仓库的完美配合。
一臻数据
2024/12/24
2470
老板既要又要还要......我用Doris+Hudi把不可能变成了日常
TinEye Reverse Search Engine_tidb和mysql
`L_EXTENDEDPRICE` decimal(12, 2) NOT NULL,
全栈程序员站长
2022/09/30
7260
经验分享 | 如何通过SQL获取MySQL对象的DDL、统计信息、查询的执行计划
说明:PawSQL项目开发的过程中,收集了一些对数据库元数据采集的SQL语句,可能对开发人员有某些帮助,在此分享出来,供大家参考,本次分享的是针对MySQL数据库的操作。
PawSQL
2024/08/20
4280
经验分享 | 如何通过SQL获取MySQL对象的DDL、统计信息、查询的执行计划
Streaming与Hudi、Hive湖仓一体!
也就是,可以将HDFS和Hudi结合起来,提供对流处理的支持能力。例如:支持记录级别的更新、删除,以及获取基于HDFS之上的Change Streams。哪些数据发生了变更。
ApacheHudi
2021/07/05
3.5K0
Streaming与Hudi、Hive湖仓一体!
字节跳动基于Doris的湖仓分析探索实践
导读:Doris是一种MPP架构的分析型数据库,主要面向多维分析、数据报表、用户画像分析等场景。自带分析引擎和存储引擎,支持向量化执行引擎,不依赖其他组件,兼容MySQL协议。
从大数据到人工智能
2022/09/14
1.2K0
字节跳动基于Doris的湖仓分析探索实践
知乎SQL优化挑战赛 - 题目1解析
最近在知乎上发起了一个SQL优化挑战赛,本文为题目1的解析。其中涉及索引失效,修饰子查询重写等优化知识点,希望对大家在学习优化SQL的过程中有所帮助。
PawSQL
2024/08/20
1530
知乎SQL优化挑战赛 - 题目1解析
知乎SQL优化挑战赛 - 题目2解析
最近在知乎上发起了一个SQL优化挑战赛,其中题目2用到了多个重写优化算法以及索引创建的策略。本文讲解了详细的优化分析过程,涉及SQL优化的多个方面,包括索引查找、避免回表、驱动表选择、索引避免排序,以及两种重写优化的应用。
PawSQL
2024/08/20
1440
知乎SQL优化挑战赛 - 题目2解析
Apache Hudi与Hive集成手册
Hudi源表对应一份HDFS数据,可以通过Spark,Flink 组件或者Hudi客户端将Hudi表的数据映射为Hive外部表,基于该外部表, Hive可以方便的进行实时视图,读优化视图以及增量视图的查询。
ApacheHudi
2021/12/09
1.8K0
Apache Hudi从零到一:深入研究读取流程和查询类型(二)
在上一篇文章中,我们讨论了 Hudi 表中的数据布局,并介绍了 CoW 和 MoR 两种表类型,以及它们各自的权衡。在此基础上我们现在将探讨 Hudi 中的读取操作是如何工作的。
ApacheHudi
2024/01/10
8860
Apache Hudi从零到一:深入研究读取流程和查询类型(二)
我是一个索引
我类似于一本书的目录,只不过书的内容是静态的,而数据是动态变化的。可以想像,如果书中的内容页频繁变化,那么更新书的目录也会花掉不少成本。所以说,我不是多多益善。
somenzz
2020/11/25
8830
我是一个索引
Apache Hudi在腾讯的落地与应用
Apache Hudi是一个基于数据库内核的流式数据湖平台,支持流式工作负载,事务,并发控制,Schema演进与约束;同时支持Spark/Presto/Trino/HIve等生态对接,在数据库内核侧支持可插拔索引的更新,删除,同时会自动管理文件大小,数据Clustering,Compaction,Cleanning等
ApacheHudi
2022/12/09
2K1
Apache Hudi在腾讯的落地与应用
Impala tpc-h sql optimize
Impala tpc-h sql 优化 因为impala 现在优化器还差点劲,只能手动改改SQL 提升下性能 下期发 impala-kudu 性能优化一个数量级(测试集 TPC-H 1TB) q1_pricing_summary_report.sql EXPLAIN SELECT L_RETURNFLAG, L_LINESTATUS, SUM(L_QUANTITY), SUM(L_EXTENDEDPRICE), SUM(L_EXTENDEDPRICE * (1
jasong
2022/05/12
6160
数据库虚拟索引工具videx简单使用
videx是字节最近开源的MySQL虚拟索引工具。项目地址 https://github.com/bytedance/videx
保持热爱奔赴山海
2025/05/14
1150
TiDB 7.5.0 LTS 高性能数据批处理方案
过去,TiDB 由于不支持存储过程、大事务的使用也存在一些限制,使得在 TiDB 上进行一些复杂的数据批量处理变得比较复杂。
PingCAP
2024/02/19
3180
「Hudi系列」Apache Hudi入门指南 | SparkSQL+Hive+Presto集成
hive 查询hudi 数据主要是在hive中建立外部表数据路径指向hdfs 路径,同时hudi 重写了inputformat 和outpurtformat。因为hudi 在读的数据的时候会读元数据来决定我要加载那些parquet文件,而在写的时候会写入新的元数据信息到hdfs路径下。所以hive 要集成hudi 查询要把编译的jar 包放到HIVE-HOME/lib 下面。否则查询时找不到inputformat和outputformat的类。
王知无-import_bigdata
2022/03/11
2.7K0
「Hudi系列」Apache Hudi入门指南 | SparkSQL+Hive+Presto集成
推荐阅读
相关推荐
Apache Doris + Paimon 快速搭建指南|Lakehouse 使用手册(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档