OkHttp之ConnectInterceptor简单分析

在《 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的整个操作。

限于篇幅原因,虽然还有好多知识点和要点没有讲解,但是为了本篇博客的主题不至于跑偏,本篇博客就此完结,关于里面涉及的另外的知识点和要点会另外写博客专门梳理,如有不当之处,欢迎批评指正

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郭梧悠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值