在《 Okhttp之CacheInterceptor简单分析 》这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览器——ConnectInterceptor,本篇就对此拦截器的工作做简单的梳理,Connect顾名思义该拦截器的主要作用是打开了与服务器的链接,正式开启了网络请求,还是从intercept方法说起:
public Response intercept(Chain chain) throws IOException {
。。。。。
//从拦截器链里得到StreamAllocation对象
StreamAllocation streamAllocation = realChain.streamAllocation();
。。。。
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
//获取realConnetion
RealConnection connection = streamAllocation.connection();
//执行下一个拦截器
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
从代码上来看该拦截器的主要功能都交给了StreamAllocation处理,且这个类是从拦截器链对象(RealInterceptorChain对象)上获取的,通过《Okhttp源码简单解析(一) 》这篇博客我们知道RealInterceptorChain是在RealCall的getResponseWithInterceptorChain方法初始化的
Response getResponseWithInterceptorChain() throws IOException {
//省略部分与本篇博客无关的代码
//将拦截器集合交给RealInterceptorChain这个Chain对象来处理
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
RealInterceptorChain构造函数第二个参数就是我们需要的StreamAllocation对象,但是,纳尼?为什么上面创建 RealInterceptorChain对象的时候第二个参数传的null?
其实,根据《Okhttp源码简单解析(一) 》这篇博客我们知道拦截器链的工作原理,且Okhttp内置拦截器链上第一个拦截器就是RetryAndFollowUpInterceptor,事实上StreamAllocation 就是在这个拦截器里面初始化的!
private StreamAllocation streamAllocation;
Response intercept(Chain chain) throws IOException {
//初始化streamAllocation对象
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
//执行procced方法,将streamAllocation对象传给拦截器链
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
}
可以看出在RetryAndFollowUpInterceptor这个拦截器会初始化一个StreamAllocation交给拦截器链(也就是说一次发起一次请求就会生成一个StreamAllocation对象,但是StreamAllocation对象公用一个连接池)。那么这个StreamAllocation到底是神马玩意?该构造器里面有两个重要的参数:
1.使用了Okhttp的连接池ConnectionPool
2.通过url创建了一个Address对象。
Okhttp连接池简单说明:
本篇只是对连接池做最简单的说明,内部的实现原理暂时不细讲。在Okhttp内部的连接池实现类为ConnectionPool,该类持有一个ArrayDeque队列作为缓存池,该队列里的元素为RealConnection(通过这个名字应该不难猜出RealConnection是来干嘛的)。
该链接池在初始化OkhttpClient对象的时候由OkhttpClient的Builder类创建,并且ConnectionPool提供了put、get、evictAll等操作。但是Okhttp并没有直接对连接池进行获取,插入等操作;而是专门提供了一个叫Internal的抽象类来操作缓冲池:比如向缓冲池里面put一个RealConnection,从缓冲池get一个RealConnection对象,该类里面有一个public且为static的Internal类型的引用:
//抽象类
public abstract class Internal {
public static Internal instance;
}
instance的初始化是在OkhttpClient的static语句块完成的:
static {
Internal.instance = new Internal() {
//省略部分代码
};
}
在文章开头ConnectionInterceptor的intercept方法中拿到StreamAllocation对象之后就调用了newStream方法,还是先说说这个方法是干什么的吧,然后在分析其源码(带着结论讲解源码能方便的说明问题),newStream方法主要做了工作:
1)从缓冲池ConnectionPool获取一个RealConnection对象,如果缓冲池里面没有就创建一个RealConnection对象并且放入缓冲池中,具体的说是放入ConnectionPool的ArrayDeque队列中。
2)获取RealConnection对象后并调用其connect**打开Socket链接**
下面就分析其源码:
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
。。。
//获取一个RealConnection对象
RealConnection resultConnection = findHealthyConnection(。。。);
//获取HttpCodec 对象
HttpCodec resultCodec = resultConnection.newCodec(client, this);
//返回HttpCodec对象
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
}
仅从上面的代码来看该方法主要就是做了两件事:
1、调用findHealthyConnection获取一个RealConnection对象。
2、通过获取到的RealConnection来生成一个HttpCodec对象并返回之。
所以,看看findHealthyConnection做了神马?
private RealConnection findHealthyConnection(。。。) {
while (true) {//一个循环
//获取RealConnection对象
RealConnection candidate = findConnection(。。。);
synchronized (connectionPool) {
//直接返回之
if (candidate.successCount == 0) {
return candidate;
}
}
//对链接池中不健康的链接做销毁处理
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
//返回
return candidate;
}//end while
}
上面代码也很简单:
1、开启一个while循环,调用findConnection继续获取RealConnection对象candidate 。
2、如果candidate 的successCount 为0,直接返回之,while循环结束
3、如果candidate是一个不“健康”的对象,则对此对象进行调用noNewStreams进行销毁处理,继续循环调用findConnection获取RealConnection对象。
(注:不健康的RealConnection条件为如下几种情况:
RealConnection对象 socket没有关闭
socket的输入流没有关闭
socket的输出流没有关闭
http2时连接没有关闭
)
所以继续看看findConnection方法做了些神马?
private RealConnection findConnection(。。。){
//选中的路由
Route selectedRoute;
synchronized (connectionPool) {
。。。。
//尝试复用
RealConnection allocatedConnection =this.connection;
//可以复用直接返回
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
//从连接池获取一个连接,此时
Internal.instance.get(connectionPool, address, this, null);
//成功从连接池中获取一个连接,返回之
if (connection != null) {
return connection;
}
//当前对象使用的路由对象
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {//阻塞操作
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
//从缓冲池中获取对象
Internal.instance.get(connectionPool, address, this, selectedRoute);
//缓存池中有此连接
if (connection != null) return connection;
//当前对象新的路由对象
route = selectedRoute;
refusedStreamCount = 0;
//缓存池中没有此连接,初始化一个 ,
result = new RealConnection(connectionPool, selectedRoute);
//将当前StreamAllocation的弱引用
//交给result的allocations集合里
//并将result赋值给this.connection这个引用
acquire(result);
}
//开始Socket连接,为阻塞操作
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// 将新创建的RealConection放入到缓冲池
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one
//如果另外的多路复用连接在同时床架,则释放此连接,用另外的链接
if (result.isMultiplexed()) {//此处代码暂不分析
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
结合代码中的注释,这个类大体上做了如下几个工作:
1、StreamAllocation的connection能复用就复用之
2、如果connection不能复用,则从连接池中获取RealConnection,获取成功则返回,从连接池中获取RealConnection的方法调用了两次
,第一次没有传Route,第二次传了(这个坑以后讲解)
3,如果连接池里没有则new一个RealConnection对象,并放入连接池中
4.最终调用RealConnection的connect方法打开一个socket链接(此处暂且说结论,至于为何断定是socket链接,篇幅有限另外结合别的知识点另开博文说明)。
到此为止,ConnectionInterceptor简单分析完毕,分析了这么多,总的来说ConnectionInterceptor就是弄一个RealConnection对象,然后创建Socket链接,并且调用下一个也是最后一个拦截器来完成Okhttp的整个操作。
限于篇幅原因,虽然还有好多知识点和要点没有讲解,但是为了本篇博客的主题不至于跑偏,本篇博客就此完结,关于里面涉及的另外的知识点和要点会另外写博客专门梳理,如有不当之处,欢迎批评指正