OkHttp源码学习过程记录
本篇主要用于记录自己学习OkHttp源码的过程,如果大家看到有错误的地方,请一定指正,发布文章最怕的就是误导别人。我会按照我个人总结的步骤来一步步学习源码,可能方式上也不是最好的,也请大家多多包涵。
OkHttp的优点
以下来自okHttp官网说明
- HTTP/2允许指向同一个Host的请求共享一个socket
- 连接池减少请求延迟
- GZIP压缩下载大小
- 通过缓存请求返回结果减少重复请求
- 使用简单,它支持同步和异步请求
针对优点逐个分析
HTTP/2允许指向同一个host的请求共享一个socket
-
HTTP/2是什么?
刚开始,HTTP/2是GOOGLE原先为了减少网页的加载延迟,开发了一个SPDY协议,后来HTTP工作组将SPDY协议作为基础,开始制定HTTP/2标准。HTTP/1.X需要使用多个连接才能实现并发与缩短延迟、不会压缩请求和响应标头以及不支持资源优先级排序导致的TCP利用率低下的问题,都在HTTP/2得到解决。
-
HTTP/2 通过支持标头字段压缩和在同一连接上 进行多个并发交换,让应用更有效地利用网络资源,减少 感知的延迟时间。具体来说,它可以对同一连接上的请求和响应消息进行交错 发送并为 HTTP 标头字段使用 有效编码。 > HTTP/2 还允许为请求设置优先级,让更重要的请求更快速地完成,从而进一步 提升性能。
-
HTTP/2性能提升的核心在于新的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务端之间传输
-
这里所谓的“层”,指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制: HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。 HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码
-
请求与响应复用
(这点就是Okhttp提到的特性)在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接(请参阅使用多个 TCP 连接)。 这是 HTTP/1.x 交付模型的直接结果,该模型可以保证每个连接每次只交付一个响应(响应排队)。 更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。
HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用: 客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。
-
数据流优先级将 HTTP 消息分解为很多独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。 为了做到这一点,HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系:
- 可以向每个数据流分配一个介于 1 至 256 之间的整数。
- 每个数据流与其他数据流之间可以存在显式依赖关系
关于OkHttp的拦截器机制
面试中经常会让讲一下okHttp中用到的设计模式,其实主要是考察一下大家对于责任链模式的理解,以及okhttp中责任链模式的运用。
首先我们看一下责任链模式的定义:
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
责任链模式的实现步骤:
-
抽象出请求对象,它将在责任链上被传递,在OKHttp上就是Chain这个类
我们通过上图可以chain是一个接口,作为实现类RealInterceptorChain才是请求对象实体,该类中含有责任链中的所有处理器的interceptor列表,当前拦截器在列表中的index,并且在proceed方法中,我们可以看到,只要拦截器中的intercept方法中调用了chain.proceed方法就可以让责任链一直传递下去// 调用下一个拦截器进行处理 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next);
-
抽象处理器,如上一步的类图中展示的那样,我们的处理器其实就是Interceptor接口,其中的intercept方法中传入的参数正是我们需要传递并处理的封装对象chain,而返回值是我们希望得到的结果对象response
下面一个图可以帮助大家理解
连接池减少请求延迟
连接池作为okhttp的核心逻辑,也是面试中经常被问到的点,这里需要我们好好学习一下。
首先,什么是连接池?
官方定义:
管理HTTP和HTTP / 2连接的重用,以减少网络延迟。共享相同地址的HTTP请求可以共享一个连接。此类实现了将哪些连接保持打开状态以备将来使用的策略。
其次,我们看一下连接池的类图
通过以上类图,我们可看到连接池使用了代理模式,真正干活的其实是RealConnection.
我们拉看一下RealConnection的构造方法,其中有三个参数
- maxIdleConnections针对每个请求地址最大空闲连接数,用于判断当前空闲连接数,如果超过了该数值,则移除该connection,这个稍后会在cleanup方法的逻辑中看到
- keepAliveDuration,一个connection最长存活时间
- timeUnit,时间单位s/ms
接下来看一下连接池的关键方法:
-
put(connection)将连接放入连接池,同时如果当前没有进行中的清理任务,则将cleanUpRunnable任务放入连接池connections,connections的类型是一个双端队列ArrayDeque,辅助以调用时序图方便大家了解该方法的调用时机
总结:
-
线程池每次请求都会经过ConnectionInterceptor处理,在ConnectionInterceptor的intercept方法拿到一个chain.transmitter对象,Transmitter(发射器)这个类很重要,官方解释为
OkHttp的应用程序和网络层之间的桥梁。此类对外暴露了高级应用程序层原语:连接,请求,响应和流。
可以理解为Transmitter负责把应用层的请求封装成网络层可识别的内容。
继续刚才的话题,在拿到transmitter对象以后,调用了transmitter.newExchange,这个方法内又调用了exchangeFinder.find方法,exchangeFinder这个类主要的职责便是从连接池中找到合适的连接,find方法内部的逻辑暂不展开,我们可以进一步看到这里也是真正realConnectionPool获取连接与放入连接池的主要内容。
这里单独把这个方法内部详细过一下。
public ExchangeCodec find( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { int connectTimeout = chain.connectTimeoutMillis(); int readTimeout = chain.readTimeoutMillis(); int writeTimeout = chain.writeTimeoutMillis(); int pingIntervalMillis = client.pingIntervalMillis(); boolean connectionRetryEnabled = client.retryOnConnectionFailure(); //需要查看findHealthyConnection内部逻辑 try { RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); return resultConnection.newCodec(client, chain); } catch (RouteException e) { trackFailure(); throw e; } catch (IOException e) { trackFailure(); throw new RouteException(e); } } …… /** *查找一个正常的连接,如果连接不正常,则不停循环请求指导找到正常的连接 */ private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException { while (true) { //findConnection才是真正的从连接池获取连接逻辑 RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); // 如果这是一个全新的连接,我们可以跳过运行状况检查。 synchronized (connectionPool) { if (candidate.successCount == 0 && !candidate.isMultiplexed()) { return candidate; } } // 针对返回的连接再次做一次是否正常的判断,如果不正常,则从连接池中移除 if (!candidate.isHealthy(doExtensiveHealthChecks)) { candidate.noNewExchanges(); continue; } return candidate; }
-