老生常谈的OkHttp

背景:

        前面我们已经梳理过HTTP相关知识点了,但是作为Android开发,大多数时候并不会直接面向HTTP协议,而是面向OkHttp这个网络请求框架来做网络请求(当然了,现在可能更多的是面向Retrofit做网络开发,但Retrofit说到底只是个工具,内部还是OKHttp完成的网络请求)。这也是为啥OkHttp变成了面试必问的原因,这里我们就简单理一下OkHttp这个框架的相关知识。

一、OkHttp的组成

        OkHttp框架一经面试就收到的热捧,这是因为它极大的简化了开发者完成网络开发的步骤。框架内部使用的大量的设计模式,但今天我们不说设计模式,我们先来说说它的主要组成。作为Android开发,OkHttpClient我想都不会陌生,因为我们的网络请求就得通过它来发出去。这也是整个OkHttp框架的入口。

通过源码很清晰的就能看到OkHttpClient的组成部分。其中被提及的最多的就是:分发器和拦截器两个成员。这也是我们将要说的重点。

  • 分发器:负责管理和分发网络请求(Call)的核心
  • 拦截器:负责网络请求的拦截、建立连接、拼包、发送/接收数据等任务

二、分发器都负责干啥

        OkHttp中的分发器可以说是除了拦截器以外最关键的一个环节了。之所以有分发器的存在是因为我们一个APP会有无数的网络请求,且可能同一时间进行大量的网络请求。这就要求OkHttp能够支持大并发的网络请求需要,同时还要支持异步请求和同步请求。

public final class Dispatcher {
//异步请求同时存在的最大请求
private int maxRequests = 64;
//异步请求同一域名同时存在的最大请求
private int maxRequestsPerHost = 5;
//闲置任务(没有请求时可执行一些任务,由使用者设置)
private @Nullable Runnable idleCallback;

//异步请求使用的线程池
private @Nullable ExecutorService executorService;

//异步请求等待执行队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

//异步请求正在执行队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

//同步请求正在执行队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
.....
}

如上源码,分发器主要有一个线程池+三个等待队列组成。至于他的工作流程我就不去贴代码一步步扣了,有兴趣的自己去跟下代码把,这里就总结下它的工作流程:

同步请求:因为同步请求是不存在任何条件限制立马执行,不需要线程池,所以分发器只将其放入同步执行队列做一下记录,等任务执行完再移除。

 异步请求:异步请求相对于同步来说要复杂一丢丢。

  • 正在执行的异步任务小于最大同时请求数(64),且同一Host请求小于最大同一Host请求数(5)则将Call加入异步正在执行队列,并交给线程池立即执行。否则将Call加入异步等待队列。

 

  • 不管是异步请求还是同步请求,任务执行完后都会执行finish操作,唯一的区别是异步请求执行finish操作时会轮询异步等待队列。

 前面也说了OkHttp能够达到高并发的要求,那么问题来了,他是怎么做到的呢?关键点就在分发器的线程池上:

线程池中常用的有三种队列,我们需要简单了解下:

(1)SynchronousQueue:是一个没有容量,无缓冲,不存储元素的阻塞队列,是个典型的生产者消费者模型,会直接将任务交给消费者,必须等队列中添加的元素被消费后才能继续添加新的元素。这样结合线程池的工作原理就不难明白为啥选择它来支持高并发了,由此提交到这个线程池的任务会被立即执行。

(2) LinkedBlockingQueue:是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待,当队列满时,才会开启新的线程,立即执行新添加的任务,当线程数达到 maximumPoolSize 数量时,执行线程拒绝策略。每个线程完全独立于其他线程。

(3)ArrayBlockingQueue:是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会执行拒绝策略。

其中LinkedBlockingQueue和ArrayBlockingQueue类似,区别就是一个无界(Integer.MAX_VALUE大小),一个有界(需要指定大小)。

三、拦截器的工作

这里我们只讲一下OkHttp内部自带的五种拦截器,因为这五种拦截器组合完成了真正的Http网络请求。自带的五大拦截器将Request按责任链模式依序向下传递,Response结果再按责任链逆序向上传递返回。也就意味着第一个执行的拦截器最先接触到Request,最后接收到Response:

  • 重试与重定向拦截器:RetryAndFollowUpInterceptor

    (1)重试

在使用者不禁用重试的前提下,当出现某些异常(如:Socket超时异常、路由异常)时,且存在更多的路由线路,则会尝试换条线路进行请求的重试。但是这里要注意有几种异常不会进行重试:

  • SSL证书异常(证书验证失败)
  • SSL验证失败异常(通常是没有证书)
  • 协议异常(未按照Http协议定义数据)

    (2)重定向

如果请求结束,也没有发生异常并不代表当前获得的响应就是最终结果,而是要根据Response响应码再次判断是否需要重定向。状态码很多,这里就不啰里吧嗦介绍了,有兴趣自己去扣一下源码

 我想强调的是,重定向是有次数限制的,最大次数为20次。

  • 桥接拦截器:BridgeInterceptor

        上面在说HTTP协议的时候已经讲过它的报文了,其中的Header的关键部分都是在这个拦截器中进行补全的。如:

请求头说明
Content-Type请求体类型,如:application/x-www-form-urlencoded
Content-Length/Transfer-Encoding请求体解析方式
Host请求的主机站点
Connection: Keep-Alive保持长连接
Accept-Encoding: gzip接受响应支持gzip压缩
Cookiecookie身份辨别
User-Agent请求的用户信息,如:操作系统、浏览器等

在补全请求头后交给下一个拦截器处理,在得到响应结果后还会做两件事:

  1. 保存cookie,在下次请求则会读取对应的数据设置进入请求头,默认的CookieJar不提供实现
  2. 如果使用gzip返回的数据,则使用GzipSource包装便于解析。
  • 缓存拦截器:CacheInterceptor

        Http的缓存机制前面在介绍Http协议的时候我们也详细讲解过它的工作流程了,在这里我也不再啰嗦它的策略机制了,有兴趣可以翻到前面去看看。缓存拦截器的作用就是在发出请求前,判断缓存是否命中。如果命中则可以不请求,直接使用缓存的响应。但是要注意OkHttp只会缓存GET请求的响应(至于为啥,我也不知道,哈哈)

  • 连接拦截器:ConnectInterceptor

        既然要通信,那必然要先建立连接,连接拦截器的工作就是获取一个与目标服务器的连接。这里要稍微讲一下在Http2.0中它的多路复用逻辑:

  1. 首先在连接池中查找DNS、代理、SSL证书、服务器域名、端口等连接参数完全相同的连接
  2. 判断连接是否被占用或被断开
  3. 满足上述条件则复用连接,否则建立新连接

这里还要提一嘴的是,在连接拦截器将请求提交给请求服务器之间,还会判断用户是否设置了网络拦截器,如果设置了先执行用户的网络拦截器。

 

  • 请求服务器连接器:CallServerInterceptor

        连接建立后剩下的就是通信发送请求获取响应了。但是前面我们也说过HTTP的POST(带请求体)和GET请求区别是一个POST请求会发两个包,第一个包先用来确认服务器是否接收请求体,如果服务器响应Expect:100-continue就表示接收请求,继续发送剩余请求数据。最后获取响应数据并解析响应。

总结:在OkHttp的整个工作流程就像个工厂流水线,分发器负责下达分发任务,拦截器负责执行,只不过拦截器需要最少经过五道工序才能得到最终成果:

1、重试重定向拦截器在交出请求(交给下一个拦截器)之前,负责判断用户是否取消了请求;在获得了结果之后,会根据异常情况以及响应码判断是否需要重试或重定向,如果满足条件那么就会重启执行所有拦截器。

2、桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP压缩);在获得了结果后,调用保存cookie的接口并解析GZIP数据。

3、缓存拦截器顾名思义在交出之前读取并判断是否使用缓存;且获得结果后判断是否缓存。

4、连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流。

5、请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值