OkHttp原理解析

一:OKHttp的介绍

OkHttp是当下Android使用最频繁的网络请求框架。

Google Android4.4 以后,开始将源码中 的HttpURLConnection 底层实现 替换为  OKHttp,(即HttpURLConnection 底层实现 改为了OKHttp)Retrofifit 框架底层同样是使用 OKHttp 的。
优点:
支持 Http1 Http2 Quic 以及 WebSocket
连接池复用底层 TCP(Socket) ,减少请求延时
无缝的支持 GZIP 减少数据流量
缓存响应数据减少重复的网络请求
请求失败自动重试主机的其他 ip ,自动重定向

二:OKHttp的使用

1. get请求

异步:

同步:第4步通过 Call.execute() 来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行网络请求

2. post请求

异步:

同步:第5步通过 Call.execute() 来提交请求,注意这种方式会阻塞调用线程,所以在Android中应放在子线程中执行网络请求

OKHttp的使用:get请求 和 post请求 ,Call call = client.newCall(request)

在使用 OkHttp 发起一次请求时,使用者最少存在 OkHttpClient Request Call 三个角色。
OkHttpClient Request 的创建,可以使用OkHttp为我们提供的 Builder (建造者模式)。
OkHttp 在设计时采用的门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端
OkHttpClient 统一暴露出来。
OkHttpClient 中全是一些配置,比如代理的配置、 ssl 证书的配置等。而 Call 本身是一个接口,我们获得的实现 为RealCall.
get请求 和 post请求 ,都执行了client.newCall(request),(client指的是OkHttpClient)
执行的流程是:
OkHttpClient.newCall() ----> RellCall # newRealCall(),  然后是进入RealCall 的enqueue 或execute 方法
如下是 OkHttpClient 的 newCall()的方法

三:Okhttp 源码分析

1. 先分析下 RealCall 的 execute() 同步方法:

首先. 执行OkHttpClient 分发器的executed()方法,如client.dispatcher.executed(this)

其次.发起请求,拿到请求结果,如Response result = getResponseWithInterceptorChain()

2. 接下来分析 RealCall 的 enqueue() 异步方法:

1. 把带有回调的repsonseCallback 封装到AsyncCall对象里,然后把AsyncCall 任务交给

OkHttpClient的分发器,如 client.dispatcher.enqueue(new AsyncCall(responseCallback))

分发器,执行异步任务式,如何把任务添加到 正在执行的队列 还是添加到 等待队列呢?如下:

3. 怎么从 等待队列里取任务 放到 正在执行的队列里呢? 整个流程如下

 

 

 

 

 

如果该 RealCall 已经执行过了,再次执行enqueue()方法是不允许的,会抛出异常。异步请求会把一个 AsyncCall 提交给分发器。
AsyncCall 实际上是一个 Runnable 的子类 , 使用线程启动一个 Runnable 时会执行 run 方法,最后执行到  AsyncCall 的  execute 方法 :

 

同时 AsyncCall 也是 RealCall 的普通内部类,这意味着它是持有外部类 RealCall 的引用,可以获得直接调用外 部类的方法。

可以看到无论是同步还是异步请求实际上真正执行请求的工作都在 getResponseWithInterceptorChain() 中。这个 方法就是整个OkHttp的核心:拦截器责任链。但是在介绍责任链之前,我们再来回顾一下线程池的基础知识。

OKHttp的调用 流程

四:OkHttp分发器线程池

1 .线程池的工作机制

corePoolSize>BlockingQueue>maximumPoolSize>拒绝策略

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

分发器就是来调配请求任务的,内部会包含一个线程池。当异步请求时,会将请求任务交给线程池
来执行。那分发器中默认的线程池是如何定义的呢?为什么这样定义呢?

OkHttp 的分发器中的线程池定义如上,其实就和 Executors.newCachedThreadPool() 创建的线程一样。
首先核 心线程为0 ,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在 60s 内没有工作就会被回收。
而最大线程数 Integer.MAX_VALUE 与等待队列 SynchronousQueue 的组合能够得到最大的吞吐量,即当需要线程池执行任务时,如果没有空闲线程也不需要等待,会新建线程去执行任务!
等待队列的不同指定了线程池的不同排队机制。一 般来说,等待队列 BlockingQueue 有: ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue
2. 使用ArrayBlockingQueue
当设置队列的容量是1,任务1一直没有执行完成,任务会一直在队列里,得不到执行,如下:

假设向线程池提交任务时,核心线程都被占用的情况下:
ArrayBlockingQueue :基于数组的阻塞队列,初始化需要指定固定大小。
当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。
LinkedBlockingQueue :基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。
当指定大小后,行为就和 ArrayBlockingQueu 一致。而如果未指定大小,则会使用默认的 Integer.MAX_VALUE 作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。
SynchronousQueue : 无容量的队列。
(无容量的队列,这样的话,就不会往队列里添加,只要任务数没有达到最大线程数,就会创建新的线程去执行执行任务)
使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败,因为是没有容量的队列,不缓存队列。而失败后如果没有空闲的非核心线程,就会检查当前线程池中的线程数,如果没有达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合 Integer.MAX_VALUE 就实现了真正的
无等待。
但是需要注意的时,我们都知道,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个数。那么当设置最大线程数为 Integer.MAX_VALUE 时, OkHttp 同时还有最大请求任务执行个数 : 64 的限制。这样即解决了这个问题同时也能获得最大吞吐。

五:拦截器责任链

OkHttp 最核心的工作是在 getResponseWithInterceptorChain() 中进行,在进入这个方法分析之前,我们先来了解什么是责任链模式,因为此方法就是利用的责任链模式完成一步步的请求。
责任链模式
在这种模式中,通常每个接收者 都包含 对另一个接收者的 引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下 一个接收者,依此类推。(A包含B, B包含C,C包含D,依次类推)
那整个过程是什么样子的呢?举例如下:

此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。 

拦截器流程
OkHttp 中的 getResponseWithInterceptorChain() 中经历的流程为

请求会被交给责任链中的一个个拦截器。默认情况下有五大拦截器:
1. RetryAndFollowUpInterceptor
第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求
2. BridgeInterceptor
补全请求,并对响应进行额外处理
3. CacheInterceptor
请求前查询缓存,获得响应并判断是否需要缓存
4. ConnectInterceptor
与服务器完成 TCP 连接
5. CallServerInterceptor
与服务器通信;封装请求数据与解析响应数据 ( 如: HTTP 报文 )

拦截器详情
一、重试及重定向拦截器
第一个拦截器 : RetryAndFollowUpInterceptor ,主要就是完成两件事情:重试与重定向。
重试
请求阶段发生了 RouteException 或者 IOException 会进行判断是否重新发起请求。
RouteException

IOException

 两个异常都是根据 recover 方法判断是否能够进行重试,如果返回 true ,则表示允许重试。

所以首先使用者在不禁止重试的前提下,如果出现了 某些异常 ,并且存在更多的路由线路,则会尝试换条线路进行 请求的重试。其中某些异常 是在 isRecoverable 中进行判断 :

1 协议异常 ,如果是那么直接判定不能重试 ; (你的请求或者服务器的响应本身就存在问题,没有按照 http 协议来 定义数据,再重试也没用)
2 超时异常 ,可能由于网络波动造成了 Socket 连接的超时,可以使用不同路线重试。
3 SSL 证书异常 /SSL 验证失败异常 ,前者是证书验证失败,后者可能就是压根就没证书,或者证书数据不正确, 那还怎么重试?
经过了异常的判定之后,如果仍然允许进行重试,就会再检查当前有没有可用路由路线来进行连接。简单来说,比 如 DNS 对域名解析后可能会返回多个 IP ,在一个 IP 失败后,尝试另一个 IP 进行重试。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OkHttp 是一个开源的 HTTP 客户端,用于 Android 平台和 Java 应用。它建立在 Java 的 HttpURLConnection 类之上,并提供了更简洁、更强大的 API。 OkHttp 的工作原理主要涉及以下几个关键组件: 1. `OkHttpClient`:这是 OkHttp 的核心类,负责配置和创建请求、设置拦截器、管理连接池等。你可以通过构建 OkHttpClient 实例来自定义请求的行为和参数。 2. `Request`:表示一个 HTTP 请求,包括 URL、请求方法(如 GET、POST)、请求体、请求头等信息。你可以通过 Request.Builder 构建一个 Request 实例。 3. `Response`:表示一个 HTTP 响应,包括响应码、响应体、响应头等信息。OkHttp 会将服务器返回的数据解析成 Response 对象。 4. `Interceptor`:拦截器用于在发送请求和接收响应之前进行一些额外的处理。OkHttp 提供了很多内置的拦截器,如重试拦截器、缓存拦截器等,同时也支持自定义拦截器。 5. `Dispatcher`:调度器负责管理请求的调度和执行。它可以控制同时并发执行的请求数量,还可以设置请求超时时间等。 6. `ConnectionPool`:连接池用于管理 HTTP 连接的复用和回收。OkHttp 会自动复用连接以减少网络延迟,提高性能。 7. `Cache`:缓存可以保存服务器返回的响应,以便在后续的请求中复用。OkHttp 支持对响应进行缓存,并提供了灵活的配置选项。 当你使用 OkHttp 发起一个网络请求时,它会通过 OkHttpClient 来创建一个 Request 对象,并通过 Dispatcher 来执行这个请求。在执行过程中,OkHttp 会根据设置的拦截器进行一系列的处理,如添加请求头、重试、缓存等。最终,OkHttp 将返回一个 Response 对象,你可以从中获取到服务器返回的数据。 总体来说,OkHttp 的工作原理是通过封装底层的 HttpURLConnection,提供了简洁易用的 API,并通过拦截器和连接池等机制优化了网络请求的性能和可定制性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值