OkHttp-ConnectInterceptor源码解析

ConnectInterceptor源码解析

本文基于okhttp3.10.0

1. 概述

ConnectInterceptor主要是用于建立连接,并再连接成功后将流封装成对象传递给下一个拦截器CallServerInterceptor与远端进行读写操作。

这个过程中会涉及比较多类我们简述下每个类的作用

  • StreamAllocation:类似一个工厂用来创建连接RealConnection和与远端通信的流的封装对象HttpCodec
  • ConnectionPool:连接池用来存储可用的连接,在条件符合的情况下进行连接复用
  • HttpCodec:对输入输出流的封装对象,对于http1和2实现不同
  • RealConnection:对tcp连接的封装

2. 源码解析

public final class ConnectInterceptor implements Interceptor {
   
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
   
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
   
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

可以看到代码非常少,主要就是从realChain中拿出StreamAllocation然后调用StreamAllocation#newStream()获取流的封装对象HttpCodec、StreamAllocation#connection()拿到连接对象RealConnection,然后将它们传递给下一个拦截器。

很显然主要逻辑是在StreamAllocation中实现的,而StreamAllocation是在第一个拦截器retryAndFollowUpInterceptor创建的直到ConnectInterceptor才使用

#RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
   
		    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    ...
}

可以看到构造方法一共传入了5个参数,我们只需要关注前三个即可

  • client.connectionPool():okhttp连接池
  • createAddress(request.url()):将url解析为Address对象
  • call:请求对象Call

2.1 ConnectionPool

连接池通过OkHttpClient实例的connectionPool()方法获取,默认初始化是在OkHttpClient.Builder构造函数

主要功能是用来缓存连接,当符合条件的时候进行连接复用,内部通过一个队列去缓存连接,当超过缓存时间后会自动清理过期连接。

先来看下ConnectionPool构造方法

private final int maxIdleConnections;//最大闲置连接数
private final long keepAliveDurationNs;//每个连接最大缓存时间

public ConnectionPool() {
   
    this(5, 5, TimeUnit.MINUTES);//默认最大缓存5个闲置连接,过期时间5分钟
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
   
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
   
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

默认最大缓存5个连接,过期时间为5分钟,存储连接是通过ConnectionPool#put()方法

private final Deque<RealConnection> connections = new ArrayDeque<>();//缓存队列
boolean cleanupRunning;//开启清理过期连接任务标记位
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

void put(RealConnection connection) {
   
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
   
      cleanupRunning = true;
      executor.execute(cleanupRunnable);//在线程池中开启清理过期连接任务
    }
    connections.add(connection);//添加到缓存队列
  }

存储连接的时候,如果没开启清理任务则开启清理过期连接任务并缓存新的连接

  private final Runnable cleanupRunnable = new Runnable() {
   
    @Override public void run() {
   
      while (true) {
   
        long waitNanos = cleanup(System.nanoTime());//获取最近一个即将过期连接的倒计时
        if (waitNanos == -1) return;//-1则代表没有缓存连接了直接return
        if (waitNanos > 0) {
   
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
   
            try {
   
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);//wait最近一个即将过期连接的倒计时后在进行检测
            } catch (InterruptedException ignored) {
   
            }
          }
        }
      }
    }
  };

清理过期连接runnable则是通过cleanup()方法获取最近一个即将过期连接的倒计时,为-1则代表缓存已经清空了直接return退出,否则wait一个即将超时的时间后在进行检查

  long cleanup(long now) {
   
    int inUseConnectionCount = 0;//使用的连接数
    int idleConnectionCount = 0;//闲置的连接数
    RealConnection longestIdleConnection = null;//闲置时间最长的连接
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
   
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
   
        RealConnection connection = i.next();//遍历缓存的连接

        if (pruneAndGetAllocationCount(connection, now) > 0) {
   //如果该连接有人使用
          inUseConnectionCount++;//使用连接数++
          continue;
        }

        idleConnectionCount++;//否则闲置连接数++

        long idleDurationNs = now - connection.idleAtNanos;//当前连接闲置时间
        if (idleDurationNs > longestIdleDurationNs) {
   //找到闲置最久的连接
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }

      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount >
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值