面试:OkHttp

OkHttp通过Dispatcher进行任务分发和请求数量控制,利用拦截器链处理请求,如RetryAndFollowUpInterceptor负责重试和重定向。其连接池ConnectionPool实现TCP连接复用,减少网络延迟。此外,OkHttp还支持应用拦截器和网络拦截器,分别处理不同阶段的请求和响应。

题目列表

  1. OkHttp基本实现原理是什么?
  2. OkHttp为什么比其他网络框架快?
  3. OkHttp的分发器是如何工作的?
  4. OkHttp的拦截器有哪些?应用拦截器和网络拦截器的区别?
  5. OkHttp如何复用TCP连接?
  6. OkHttp的连接池如何运行,一个链接能否并发请求网络?

1. OkHttp基本实现原理是什么?

OkHttp框架主要由3部分组成:Dispatcher(任务分发器)、Interceptor(拦截器)、ConnectionPool(TCP连接池)。

主要执行流程:

  • 当我们发起一个请求时,请求任务会被添加到Dispatcher中。
  • 在Dispatcher中会经过一些列的逻辑处理,随后会将请求传递到5个Interceptor中进行处理(Interceptor通过责任链模式进行串联)。
  • 在ConnectInterceptor中会执行从ConnectionPool中获取可复用连接的逻辑。
  • 最后传递给下一个拦截器CallServerInterceptor与服务端进行通信,从而获取到响应报文。

2. OkHttp为什么比其他网络框架快?

OkHttp的优势: 请求数的控制、连接池复用。

  • 请求数的控制

在Dispatcher中会控制最大请求数(默认64个)和同一个主机最大请求数(默认5个)。

  • 连接池复用

我们知道Http协议是应用层协议,在传输层使用的是TCP协议,而TCP协议是面相连接的,即在数据传输前后需要进行3次握手和4次挥手。当请求数量很大时,建立连接的时间损耗就会被放大。因此在OKHttp框架内部使用了连接池复用机制来避免重复建立连接。

  • 请求恢复

OkHttp处理了一些网络问题,会从很多常用的连接问题中自动恢复。如果服务器配置多个IP地址,当前一个IP地址连接失败时,OkHttp会自动尝试连接下一个IP。

3. OkHttp的分发器是如何工作的?

分发器主要作用是对请求任务的分发和请求数量的控制。

线程池:

Dispatcher(分发器)中含有1个newCacheThreadPool线程池,这个线程池的特点是只要有任务来,且没有空闲线程,就会创建一条新线程来执行任务。这就会存在资源浪费的情况(如同一时刻线程数创建过多,请求数过多等问题)。

为了优化上面的问题,Dispatcher就必须控制请求的数量,避免线程池的过多创建等问题,因此引入了队列和最大请求数的限制来进行优化。

请求数限制 + 队列:

参数含义
maxRequests最大并发请求数为64
maxRequestsPerHost每个主机最大请求数为5
runningSyncCalls正在运行的,同步的任务集合。仅用来引用正在运行的同步任务以判断并发量。
readyAsyncCalls异步的缓存,正在准备被消费的
runningAsyncCalls正在运行的 异步的任务集合,仅用来引用正在运行的任务以判断并发量。

代码分析请参考:OkHttp(一) — OkHttp 调用流程分析

4. OkHttp的拦截器有哪些?应用拦截器和网络拦截器的区别?

拦截器含义
interceptors应用拦截器。
RetryAndFollwUpInterceptor负责失败重连以及重定向,默认最多重试20次。
BridgeInterceptor负责请求和响应的转换。
CacheInterceptor负责处理缓存,默认只支持Get请求。
ConnectInterceptor负责从链接池中复用连接。
networkInterceptors网络拦截器。
CallServerInterceptor负责数据传输。

应用拦截器和网络拦截器的区别?

调用次数:应用拦截器只会调用1次。网络拦截器可能会调用0次(从CacheInterceptor获取缓存)、1次、多次(重定向等)。
请求信息:应用拦截器只能获取原始request的信息,网络拦截器可以获取请求报文headers等信息(BridgeInterceptor中完善了请求头)。

5. ConnectionPool连接池原理?

由于HTTP是基于TCP,TCP连接时需要经过三次握手,为了加快网络访问速度,可以将请求报文首部的 Connection属性设置为 Keep-Alive 来实现复用连接。

OkHttp 支持5个并发Keep-Alive,默认链路生命周期为5分钟 (链路空闲后的存活时长)。连接池有ConectionPool实现,对连接进行回收和管理。

连接池的清理:
ConectionPool 在内部使用一个子线程来定时清理连接。

清理链接时分两种情况:连接池有链接、连接池没有链接。

  • 连接池中有连接:执行cleanRunnable任务,内部调用 cleanup() 方法完成清理。
    1. 执行cleanup() 方法清理,并返回下次需要清理的间隔时间。
    2. 调用调用 wait() 方法释放 ConectionPool 这个类锁。
    3. 等 wait 时间到了以后,重新进入 while(true) 循环进行下一次的清理,并返回下一次需要清理的时间间隔,以此循环往复。
  • 连接池中没有连接:cleanup()返回-1,跳出循环,下次有连接加进来时,再次开启线程进行循环清理。之所以连接池线程可以跳出循环,是因为cleanRunnable是在子线程执行。

cleanup方法执行逻辑:

  1. 先统计空闲连接数量。
  2. 然后通过for循环查找最长空闲时间的连接以及对应空闲时长。
  3. 然后判断这个最长空闲时间的连接是否超出最大空闲连接数或者或者超过最大空闲时间,满足其一则清除最长空闲的连接。
  4. 第3步如果不满足清理条件,则返回一个对应等待时间。这个对应等待的时间又分二种情况:
    • 有空闲连接,则返回 keepAliveDurationNs - longestIdleDurationNs
    • 没有空闲的连接,则返回 keepAliveDurationNs
      注意:清除一个空闲连接后,会返回0,再次立即开始清理。

如何判断连接是否是空闲的?

StreamAllocation创建或者复用一个Connection后,会将自己添加到Connection的connection.allocations列表中,数据读取完毕之后,会将自己从Connection的connection.allocations中移除,所以判读一个Connection是否是空闲连接可以采用引用计数法,判断connection.allocations列表中是否有StreamAllocation,如果没有就是空闲连接,否则不是。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值