目录
4. OkHttp的拦截器有哪些?应用拦截器和网络拦截器的区别?
OkHttp流程图,如下:
案例,普通的okhttp同步请求。
public String get(String url) throws IOException {
//创建 OKHttpClient对象
OkHttpClient client = new OkHttpClient();
//创建 Request对象
Request request = new Request.Builder()
.url(url)
.build();
//创建 Response接收响应
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
}else{
throw new IOException("Unexpected code " + response);
}
}
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就必须控制请求的数量,避免线程池的过多创建等问题,因此引入了队列和最大请求数的限制来进行优化。
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方法执行逻辑:
先统计空闲连接数量。
然后通过for循环查找最长空闲时间的连接以及对应空闲时长。
然后判断这个最长空闲时间的连接是否超出最大空闲连接数或者或者超过最大空闲时间,满足其一则清除最长空闲的连接。
第3步如果不满足清理条件,则返回一个对应等待时间。这个对应等待的时间又分二种情况:
有空闲连接,则返回 keepAliveDurationNs - longestIdleDurationNs。
没有空闲的连接,则返回 keepAliveDurationNs。
注意:清除一个空闲连接后,会返回0,再次立即开始清理。
如何判断连接是否是空闲的?
StreamAllocation创建或者复用一个Connection后,会将自己添加到Connection的connection.allocations列表中,数据读取完毕之后,会将自己从Connection的connection.allocations中移除,所以判读一个Connection是否是空闲连接可以采用引用计数法,判断connection.allocations列表中是否有StreamAllocation,如果没有就是空闲连接,否则不是。