Okhttp源码分析

基本流程

  1. 创建RequestBody

  2. 创建Request

  3. 创建OkhttpClient

  4. 调用newCall创建Call对象

  5. 执行异步或同步操作。

  6. 创建Socket连接

  7. 发送请求并处理返回结果

创建RequestBody

RequestBody主要通过writeTo方法将请求内容写入到BufferedSinkRequestBody提供了3个create静态方法来创建RequestBody,此外RequestBody还包含两个子类。

image-20211114170629029

FormBody用于表单提交,MultipartBody用于多内容提交。

创建Request

Request对象包含如下信息

创建OkHttpClient

OkHttpClient可配置的信息包括如下:

调用newCall创建Call对象

image-20211114170500713

OkHttpClient继承Call.Factory

OkHttpClientnewCall方法调用

执行网络请求

image-20211114173043097

Call支持执行网络请求的方法enqueueexecute()enqueue执行异步请求,execute()执行同步请求。

enqueue()

Dispatcher

promoteAndExecute()

executeOn()

AsyncCallexecuteOn方法

拦截器

RealInterceptorChainproceed会创建一个InterceptorChain,并调用拦截器的intercept方法。除了CallServerInterceptor外,所有拦截器的intercept都会调用创建的InterceptorChainproceed方法。

getResponseWithInterceptorChain()

proceed()

缓存分析

Cache

缓存核心类是Cache对象。

Cache的构造函数。

Cache提供了两个方法用于存储和获取缓存。

put方法将缓存存储到磁盘上。

get方法获取缓存

CacheInterceptor

Cache这两个方法都被CacheInterceptor对象调用。所以处理缓存的核心逻辑都在CacheInterceptor中。

缓存逻辑

isCacheable方法用来判断是否可以缓存。

这里面加了一堆判断,这里需要先了解一下HTTP的缓存原理。才能搞清楚这些判断的逻辑。

Http缓存原理

在HTTP 1.0时代,响应使用Expires头标识缓存的有效期,其值是一个绝对时间,比如Expires:Thu,31 Dec 2020 23:59:59 GMT。当客户端再次发出网络请求时可比较当前时间 和上次响应的expires时间进行比较,来决定是使用缓存还是发起新的请求。

使用Expires头最大的问题是它依赖客户端的本地时间,如果用户自己修改了本地时间,就会导致无法准确的判断缓存是否过期。

因此,从HTTP 1.1 开始使用Cache-Control头表示缓存状态,它的优先级高于Expires,常见的取值为下面的一个或多个。

  • private,默认值,标识那些私有的业务逻辑数据,比如根据用户行为下发的推荐数据。该模式下网络链路中的代理服务器等节点不应该缓存这部分数据,因为没有实际意义。

  • public 与private相反,public用于标识那些通用的业务数据,比如获取新闻列表,所有人看到的都是同一份数据,因此客户端、代理服务器都可以缓存。

  • no-cache 可进行缓存,但在客户端使用缓存前必须要去服务端进行缓存资源有效性的验证,即下文的对比缓存部分,我们稍后介绍。

  • max-age 表示缓存时长单位为秒,指一个时间段,比如一年,通常用于不经常变化的静态资源。

  • no-store 任何节点禁止使用缓存。

强制缓存

在上述缓存头规约基础之上,强制缓存是指网络请求响应header标识了Expires或Cache-Control带了max-age信息,而此时客户端计算缓存并未过期,则可以直接使用本地缓存内容,而不用真正的发起一次网络请求。

协商缓存

强制缓存最大的问题是,一旦服务端资源有更新,直到缓存时间截止前,客户端无法获取到最新的资源(除非请求时手动添加no-store头),另外大部分情况下服务器的资源无法直接确定缓存失效时间,所以使用对比缓存更灵活一些。

使用Last-Modify / If-Modify-Since头实现协商缓存,具体方法是服务端响应头添加Last-Modify头标识资源的最后修改时间,单位为秒,当客户端再次发起请求时添加If-Modify-Since头并赋值为上次请求拿到的Last-Modify头的值。

服务端收到请求后自行判断缓存资源是否仍然有效,如果有效则返回状态码304同时body体为空,否则下发最新的资源数据。客户端如果发现状态码是304,则取出本地的缓存数据作为响应。

使用这套方案有一个问题,那就是资源文件使用最后修改时间有一定的局限性:

  1. Last-Modify单位为秒,如果某些文件在一秒内被修改则并不能准确的标识修改时间。

  2. 资源修改时间并不能作为资源是否修改的唯一依据,比如资源文件是Daily Build的,每天都会生成新的,但是其实际内容可能并未改变。

建立连接

image-20211114174719516

OkHttp通过ConnectInterceptor建立连接。在ConnectInterceptorintercept方法中,通过调用RealCallinitExchange方法初始化一个Exchange对象。Exchange对象负责交换数据。

initExchange()

find()

findHealthyConnection()

findConnection()

  1. 如果call里面的connection不为空,则复用call里面的connection。

  2. 如果call中的connection为空,则从连接池中获取一个。

  3. 如果缓存池里也没有就创建一个

OkHttp连接复用

OkHttp 连接复用主要是通过 RealConnectionPool 来实现的,其内部定义了一个 ConcurrentLinkedQueue 来存储创建的 RealConnection。当获取连接时,优先判断 Call 中的`` Connection 是否为空,如果不为空则直接复用,为空则从连接池中获取,当从连接池中获取不到的时候才调用 RealConnection 构造函数创建一个新的连接并添加到 RealConnectionPool 中。RealConnectionPool 提供了两个可配置的参数:最大空闲连接数和存活时长,这两个参数默认是 5 个和 5 分钟,当然我们也可以在OKHttpClient中自己配置。当向RealConnectionPool` 中添加连接和连接变成空闲时,内部都会遍历队列中所有的连接,如果最大空闲连接数或者存活时长都大于设定的则移除存空闲时长最长的连接,该方法会多次执行,直到最大空闲连接数和存活时长都小于设定的值。

image-20211115105736461

ConnectionPool

清理连接

image-20211115114825724

执行网络请求

参考

最后更新于