OkHttp 源码分析

流程分析

咱们从一个简单的 HTTP 请求开始:java

client = new OkHttpClient();
Request request = new Request.Builder().url("your url").build();
//同步发起请求
Response syncResponse = client.newCall(request).execute();
//异步发起请求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) { }
    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { }
});
复制代码

上面的代码将会发起两个简单的 HTTP 请求,请求流程以下图所示。缓存

上面的流程图只画到了责任链的部分,前面的介绍完后会单独介绍责任链及每一个 interceptor 的原理。服务器

OkHttpClient

咱们使用 new OkHttpClient() 建立一个默认的 OkHttpClient,一样也可使用 OkHttpClient.Builder 来经过自定义的参数来构造一个 client。 后面咱们使用网络请求时都将经过这个 client 来进行,能够将它理解为整个 OkHttp 的核心类,其对总体 OkHttp 进行了封装,对外提供了请求的发起以及一些参数配置的接口,咱们能够经过 OkHttpClient.Builder 来设置,对内负责协调各个类的运做,自己其实并无包含过多的代码。cookie

Request

Request 很好理解,负责组装请求。网络

Call(RealCall)

而后调用 client.newCall(request) 方法,该方法是指建立一个新的将要被执行的请求,经过 newCall 方法获取一个 Call 对象(实现为 RealCall),这时咱们使用 Call 的 execute/enqueue 将发起一个同步/异步请求。框架

因此每个 Request 最终将会被封装成一个 RealCall 对象,RealCall 与 Request 是一一对应的关系,Call 用来描述一个可被执行、中断的请求,咱们每发起一个请求时就会建立一个 RealCall 对象,最终调用 RealCall#getResponseWithInterceptorChain() 发起请求,该方法将返回一个响应结果 Response。异步

Dispatcher

Dispatcher 用于管理其对应 OkHttpClient 的全部请求,经过上面的流程图能够看到,使用异步请求时会将请求委托给 Dispatcer 对象来处理,Dispatcher 对象随 OkHttpClient 建立而建立编辑器

实际上,Dispatcher 不只用于管理异步请求,也负责管理同步请求,当咱们发起一个请求时,不管是异步仍是同步都会被 Dispatcher 记录下来。咱们能够经过 OkHtpClient#dispatcher() 获取 Dispatcher 对象对请求进行统一的控制,例如结束全部请求、获取线程池等等。 Dispatcher 中包含三个队列:ide

  • readyAsyncCalls:一个新的异步请求首先会被加入该队列中
  • runningAsyncCalls:当前正在运行中的异步请求
  • runningSyncCalls:当前正在运行的同步请求

Dispatcher 中包含一个默认的线程池用于执行全部的异步请求,也能够经过构造器指定一个线程池,全部的异步请求都将会经过这个线程池来执行。异步请求与同步请求同样,最终也会调用 RealCall#getResponseWithInterceptorChain() 发起请求,只不过一个是直接调用,一个是在线程池中调用。源码分析

经过上面的介绍已经发现了关键之处就在于那个名字超长的方法,只要调用了它就能返回一个 Response,这个方法就开始涉及到广为人知的 OkHttp 的责任链模式了。

OkHttp 责任链

讲真的,网上如今随便搜搜 OkHttp 源码的都是在将责任链,搞的我都不想讲了,可是做为一个 OkHttp 源码分析的文章不讲又感受过不去,那仍是说一下吧(这里点名批评一些为知笔记的 MarkDown 编辑器,写了一下午的东西说没就没,丝毫没有给我反应的余地)。

这一切都还要从那个名字超长的方法开始提及,咱们知道,不管如何都会调用 RealCall#getResponseWithInterceptorChain() 发起请求并获取最终的 Response。

这个方法会根据用户设置的 Interceptor 以及默认的几个 Interceptor 组装 Interceptor 列表,而后建立责任链。责任链建立好后会调用其 process 方法获取 Response 并返回,其中涉及两个概念:Interceptor、Chain

Interceptor

Interceptor 接口做为一个拦截器的抽象概念,被设计为责任链上的单位节点,用于观察、拦截、处理请求等,例如添加 Header、重定向、数据处理等等。 Interceptor 之间互相独立,每一个 Interceptor 只负责本身关注的任务,不与其余 Interceptor 接触。 Interceptor 接口中只包含一个方法(OkHttp 如今已经用 Kotlin 重写了):

interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response
}
复制代码

intercept 方法接收一个 Chain 做为参数,并返回一个 Response,该方法的通常处理逻辑以下:

上面的流程中的步骤并非必选的,也不是必定要按照这个步骤来,你彻底能够按照本身的想法进行你喜欢的各类骚操做。 在 RealCall 中会按照顺序添加以下几个默认的 Interceptor 到责任链中用来完成基本功能:

  • 用户设置的 Interceptor
  • RetryAndFollowUpInterceptor:失败重试及重定向
  • BridgeInterceptor:处理网络 Header、Cookie、gzip 等
  • CacheInterceptor:管理缓存
  • ConnectInterceptor:链接服务器
  • 若是是 WebSocket 请求则添加对应的 Interceptors
  • CallServerInterceptor:数据发送/接收

后面再详细介绍这几个 Interceptor 的具体含义及原理。 责任链将会按照添加的顺序依次执行这些 Interceptor,因此,顺序是很重要的,经过这些 Interceptor 的处理,最终会返回一个完美的 Response 给到 RealCall 里面的那个名字超长的方法,而后在返回到下游用户。至此,一个完整的请求就落下帷幕了。

Chain

Chain 被用来描述责任链,经过其中的 process 方法开始依次执行链上的每一个节点,并返回处理后的 Response。 Chain 的惟一实现为 RealInterceptorChain(下文简称 RIC),RIC 能够称之为拦截器责任链,其中的节点由 RealCall 中添加进来的 Interceptor 们组成。因为 Interceptor 的互相独立性,RIC 中还会包含一些公共参数及共享的对象。

Interceptor 与 Chain 彼此互相依赖,互相调用,共同发展,造成了一个完美的调用链,下面来看下他们的调用关系图:

经过上图能够明确的看到,当咱们在某个 Interceptor 中调用 Chain#process 方法获取 Response 时,将会依调用当前位置以后的 Interceptor 来处理这个请求,处理完成后把 Response 返回到当前 Interceptor,而后处理完再向上级返回,直到遍历结束。

网络链接与数据收发

上面已经介绍了 OkHttp 的基本概念、基础配置、线程控制、责任链,下面再说说一个网络框架的灵魂:网络请求的创建与数据收发。 RealCall 中添加的几个不一样的 Interceptor 就互相协做完成了这些功能,只要明白了这几个基础的 interceptor 就明白了 OkHttp 的灵魂。 其实我不太建议阅读源码时太过关心实现细节,只要明白设计思路,大致上的实现就差很少了,否则容易被负责的细节绕晕。 那么在介绍这几个 interceptor 以前先介绍一些 OkHttp 中的基本概念。

链接如何创建

咱们以前看的 Volley 啊等等不少网络请求框架不少底层都是经过 HTTPURLConnection 来与服务端创建链接的,而 OkHttp 就比较优秀了。由于 HTTP 协议是创建在 TCP/IP 协议基础之上的,底层仍是走的 Socket,因此 OkHttp 直接使用 Socket 来完成 HTTP 请求。

Route

route 为用于链接到服务器的具体路由。其中包含了 IP 地址、端口、代理等参数。 因为存在代理或者 DNS 可能返回多个 IP 地址的状况,因此同一个接口地址可能会对应多个 route。 在建立 Connection 时将会使用 Route 而不是直接用 IP 地址。

RouteSelector

Route 选择器,其中存储了全部可用的 route,在准备链接时时会经过 RouteSelector#next 方法获取下一个 Route。 值得注意的是,RouteSelector 中包含了一个 routeDatabase 对象,其中存放着链接失败的 Route,RouteSelector 会将其中存储的上次链接失败的 route 放在最后,以此提升链接速度。

RealConnection

RealConnection 实现了 Connection 接口,其中使用 Socket 创建 HTTP/HTTPS 链接,而且获取 I/O 流,同一个 Connection 可能会承载多个 HTTP 的请求与响应。 其实能够大概的理解为是对 Socket 、I/O 流以及一些协议的封装,这个里面涉及到的计算机网络相关的知识较多,例如 TLS 握手,HTTPS 验证等等。

RealConnectionPool

这是用来存储 RealConnection 的池子,内部使用一个双端队列来进行存储。 在 OkHttp 中,一个链接(RealConnection)用完后不会立马被关闭并释放掉,并且是会存储到链接池(RealConnectionPool)中。 除了缓存链接外,缓存池还负责按期清理过时的链接,在 RealConnection 中会维护一个用来描述该链接空闲时间的字段,每添加一个新的链接到链接池中时都会进行一次检测,遍历全部的链接,找出当前未被使用且空闲时间最长的那个链接,若是该链接空闲时长超出阈值,或者链接池已满,将会关闭该链接。 另外 RealConnection 中还维护一个 Transmitter 的弱引用列表,用来存储当前正在使用该链接的 Transmitter。当列表为空时表示该链接已经未在使用。

ExchangeCodec

ExchangeCodec 负责对 Request 编码及解码 Response,也就是写入请求及读取响应,咱们的请求及响应数据都经过它来读写。 因此 Connection 负责创建链接,ExchangeCodec 负责收发数据。 ExchangeCodec 接口的实现类有两个:Http1ExchangeCodec 及 Http2ExchangeCodec,分别对应两种协议版本。

Exchange

Exchange 功能相似 ExchangeCodec,但它是对应的是单个请求,其在 ExchangeCodec 基础上担负了一些链接管理及事件分发的做用。 具体而言,Exchange 与 Request 一一对应,新建一个请求时就会建立一个 Exchange,该 Exchange 负责将这个请求发送出去并读取到响应数据,而发送与接收数据使用的是 ExchangeCodec。

Transmitter

Transmitter 是 OkHttp 网络层的桥梁,咱们上面说的这些概念最终都是经过 Transmitter 来融合在一块儿,并对外提供功能实现。

好了,如今基本概念介绍完毕,开始看看 interceptor 吧。

RetryAndFollowUpInterceptor

这个 interceptor 顾名思义,负责失败重试以及重定向。 可能出触发重试或重定向的条件以下:

  • 401:未受权
  • 407:代理未受权
  • 503:服务未受权
  • 3xx:请求重定向
  • 408:请求超时
  • 以及一些 I/O 异常等等链接失败的状况

下面看一下其中的逻辑:

咱们上面说过,由于代理及 DNS 的缘由,对于同一个 url 可能会有多个 IP 地址,链接时经过 RouteSelector 选择合适的 Route 进行链接,因此这里的失败重试并非指对同一 IP 地址的屡次重试,是逐个尝试路由表中的地址。 上面链接失败以后,进行重试时虽然并无其它操做,但实际上开始链接时会自动调用下一个 Route 进行链接。

上图流程中有两处重点这里再介绍一下,分别是 followUpRequest 及 recover 方法。 recover 方法用于判断链接是否能够恢复重试,代码以下:

private fun recover( e: IOException, transmitter: Transmitter, requestSendStarted: Boolean, userRequest: Request ): Boolean {
    // 用户设置的是否重连参数
    if (!client.retryOnConnectionFailure) return false
    // 单次请求或 FileNotFoundException 异常不可恢复 
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
    // 不可恢复的异常,例如协议错误、验证错误等
    if (!isRecoverable(e, requestSendStarted)) return false
    // 没有更多的路由了
    if (!transmitter.canRetry()) return false
    return true
  }
复制代码

如今再来看看 followUpRequest 方法,这个代码较多就不贴了。 当走到这个方法时意味着已经链接到服务器并接收到响应了,此时须要经过响应码来判断是否须要重定向。

若是响应码为 401 或者 407 则表示请求未认证,此时从新对请求进行认证,而后返回认证后的 Request。

响应码为 3xx 表示重定向,此时重定向地址在响应 Header 的 Location 字段中,而后经过这个新的地址以及以前的 Request 构建一个新的 Request 并返回。

响应码 503 表示服务器错误,但这个是暂时的,可能立刻就会恢复,因此会直接返回以前的请求。

BridgeInterceptor

BridgeInterceptor 是用户与网络之间的桥梁,负责将用户请求转换为网络请求,也就是根据 Request 信息组建网络 Header 以及设置响应数据。 实际上,BridgeInterceptor 除了设置例如 Content-Length、Connect、Host 之类的基本请求头以外,还负责设置 Cookie 以及 gzip. BridgeInterceptor 在开始进行网络请求以前会先经过 url 判断是否有 cookie,有的话就会把这个 cookie 带上,请求结束后一样也会判断响应头是否包含 Set-Cookie 字段,包含则会将其存下来下次使用。可是存储 Cookie 的操做会委托给 CookieJar 来实现,OkHttp 默认提供了一个空的 CookieJar 对象,也就是说默认不会作出任何操做,但能够在建立 OkHttp 时指定一个本身的 CookieJar 来使用。

若是 Request 请求头中没有包含 Accept-Encoding 以及 Range 字段则会给其添加一个 "Accept-Encoding: gzip" 请求头,接收到响应数据后若是响应表示使用了 gzip 则会把响应数据交给 okio 的 GzipSource 解码。

CacheInterceptor

CacheInterceptor 负责缓存响应数据。 该方法首先会经过 Cache 对象尝试获取缓存的数据,而后再经过 CacheStrategy 获取缓存策略,经过该策略的计算结果,咱们能够获取到两个可空对象:networkRequest 以及 cacheResponse。 其中 networkRequest 为原始 Request 但可能为空,具体是否是空的经过 CacheStrategy 控制。 cacheResponse 是经过 Cache 获取到的 Response,同上,一样也可能为空。 而后就能够经过判断两个对象的可空性来处理缓存,逻辑以下:

  • 若是二者都为空,则表示既禁止了使用网络请求,也不可使用缓存或者未命中缓存,直接返回 504 错误。
  • 若是只有 networkRequest 为空,表示禁止了网络请求,此时直接返回从缓存中命中的 Response。
  • 若是二者都不为空,则开始发起请求并获取响应数据。
  • 若是此时 cacheResponse 不为空,且响应码为 304,直接返回 cacheResponse,并使用响应数据更新缓存。
  • 若是 cacheResponse 为空则会将响应数据存储到 Cache 中。
  • 返回响应数据。

须要注意,上面说的 Cache 对象默认为空,若是为空则与其相关的操做都不会被执行,且 cacheResponse 必定为空。 咱们能够在 OkHttpClient 中设置 Cache。

ConnectInterceptor

ConnectInterceptor 用来打开一个到服务端的链接。 其中代码很简单,会经过 Transmitter#newExchange 方法建立一个 Exchange 对象,并调用 Chain#process 方法。

newExchange 方法中会先经过 ExchangeFinder 尝试去 RealConnectionPool 中寻找已存在的链接,未找到则会从新建立一个 RealConnection 并开始链接,而后将其存入 RealConnectionPool,此时已经准备好了 RealConnection 对象,而后经过请求协议建立不一样的 ExchangeCodec 并返回。具体细节上面已经说过了,这里不作详细介绍。 经过上面面步骤建立好 ExchangeCodec 以后,再根据它以及其余参数建立 Exchange 对象并返回。

ConnectInterceptor 将 newExchange 方法返回的 Exchange 对象做为参数,调用 Chain#process 方法。

CallServerInterceptor

CallServerInterceptor 负责读写数据。 这是最后一个 interceptor 了,到了这里该准备的都准备好了,经过它,将会把 Request 中的数据发送到服务端,并获取到数据写入 Response。

上图为该 interceptor 总体流程图。其中操做主要都放在了 Exchange 等对象中,这里不作过多介绍。

那么 OkHttp 的源码到这里就分析的差很少啦,其实还有不少东西都没有讲到,OkHttp 是个庞大的框架,其中涉及到的东西实在太多了,并且包括了不少计算机网络的基础知识,奈何本人才疏学浅,就只讲这么多吧。

若是以为还不错的话,欢迎关注个人我的公众号,我会不按期发一些干货文章~

相关文章
相关标签/搜索