美文网首页
Android OkHttp根据EventListener回调顺

Android OkHttp根据EventListener回调顺

作者: 巴黎没有摩天轮Li | 来源:发表于2020-09-10 22:19 被阅读0次

前言

面试官:OkHttp如何进行请求日志统计,各个请求环节的耗时统计呢?
答:OkHttp提供了一个网络事件的回掉EventListener,可以统一处理各个环节进行耗时统计。

OkHttp#EventListener 回调原理

public abstract class EventListener {
   // 按照请求顺序回调
    public void callStart(Call call) {}
    // 释放当前Transmitter的RealConnection
    public void connectionReleased(Call call, Connection connection) {}
    // 只要拿到了RealConnection就会回调 分为三种情况分别是从当前Transmitter中的RealConnection复用,连接池中获取RealConnection,和新创建一个RealConnection会回调一次。
    public void connectionAcquired(call, result){};
    // 域名解析
    public void dnsStart(Call call, String domainName) {}
    public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {}
    // 开始连接
    public void connectStart(call, route.socketAddress(), proxy){}
    public void callEnd(Call call) {}  
    public void callFailed(Call call, IOException ioe) {}
}

其实探索EventListener各个回调的位置,基本上将OkHttp的请求环节都走了一遍,挺有价值的一次学习。
先看这三个回调的触发位置,我们知道OkHttp通过Dispatcher调度器(两个异步队列,一个同步队列)进行网络请求的任务调度,最终的请求过程执行在RealCall类中。所以以上三个回调将在RealCall中体现。

RealCall

// 同步
@Override public Response execute() throws IOException {
      //... 省略代码
      transmitter.callStart();
}
// 异步
@Override public void enqueue(Callback responseCallback) {
      //... 省略代码
      transmitter.callStart();
}

callStart()没的说,在调用同步异步请求的时候就会回调这个方法,而且每次请求只会调用一次。
我们了解无论执行同步异步都会最终走OkHttp提供的5个拦截器,第一个重定向拦截器只有当一次请求结束,责任链反向回传Response才能知道当前的请求地址是否被重定向,所以在这里只有可能回调callEnd()、callFailed()这里之后说。第二个拦截器是BridgeInterceptor用于将客户端的请求转换为服务端需要的请求,也就是配置请求报文头相关的信息,这里也不涉及请求的过程只是配置请求。第三个缓存拦截器,处理请求缓存的,一旦该次请求被判断满足直接使用缓存的条件,直接就返回缓存Response,因此后续的EnventListener回调事件就不会再回调 直接会回调callEnd()或者callFailed() ,这里我们为了全面就忽略缓存了。第四个拦截器连接拦截器,就涉及很多了,其主要目的将Transmitter与ExchangeFinder构建出来,此类公开了高级应用程序层原语:连接,请求,响应和流,它持有okhttpclient对象以及RealCall对象。ExchangeFinder从连接池中找到一个健康可用的连接进行复用,没有找到的话,就创建一个路由列表,并创建一个新连接,因此我们的dnsStart()回调就在这个创建新连接的过程中体现。第五个CallServerInterceptor拦截器作用就是由客户端向服务端拿数据的过程,ConnectInterceptor中建立连接后通过责任链将Exchange与最终的Request传递给当前拦截器,并发起最终的请求,获取Response返回。

ConnectInterceptor

获取构建Http1ExchangeCodec路径

ConnectInterceptor -> 
Transmitter.newExchange(xxx,xxx) -> 
ExchangeFinder.find(xxx,xxx,xxx) -> 
ExchangeFinder.findHealthyConnection() ->
ExchangeFinder.findConnection()

ok 以上是层层调用,最终到达ExchangeFinder.findConnect()的方法,这里进行了是否能找到一个健康可用的连接通道,也就是keepalive状态的Http请求,dns解析,挥手,等等过程都建立好了,没有找到就要重新走各个建立连接的流程。

ExchangeFinder

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
      //... 省略
      // 是否要断开Socket
      Socket toClose;
      // 最终请求的连接通路
      RealConnection result = null;
      // 要释放掉的连接通路
      RealConnection releasedConnection;
      releasedConnection = transmitter.connection;
      // 是否需要断开 条件是当前的RealConnection不为空,并且该RealConnection的noNewExchanges是否还可以进行创建连接
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;
      if (transmitter.connection != null) {
          // 如果当前已经有了一个满足使用的连接通路就直接用这个通道
          result = transmitter.connection;
          // 因为找到了满足通路的connection,所以要被释放的RealConnect设置为空
          releasedConnection = null;
      }
      // 若没有
      if (result == null) {
        // 去连接池中找一个满足条件的连接通路
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
          selectedRoute = transmitter.connection.route();
        }
      }
      // 断开socket
      closeQuietly(toClose);
      // 若希望被释放掉Kill掉的RealConnection不为空
     if (releasedConnection != null) {
        // 直接回调
        eventListener.connectionReleased(call, releasedConnection);
      }

     // 若从连接池中找到了一个可用健康的连接通路,就回调这个Event
     if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
      }
       // 若找到了RealConnection连接通路就返回
     if (result != null) {
        // If we found an already-allocated or pooled connection, we're done.
        return result;
      }

       // 下方的代码将重新构建一个RealConnection
      // 从连接池中若没有找到,则要重新创建一个
      boolean newRouteSelection = false;
      if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        // 1 
        routeSelection = routeSelector.next();
      }

     List<Route> routes = null;
     synchronized (connectionPool) {
      if (transmitter.isCanceled()) throw new IOException("Canceled");

      if (newRouteSelection) {
        routes = routeSelection.getAll();
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        }
      }
      // 在连接池中没有找到可用连接
      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          // 从上方1处 刚刚创建的
          selectedRoute = routeSelection.next();
        }
        // 再次创建一个RealConnection
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }
       // TCP + TLS 握手过程...
      result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
      // 关闭socket
      closeQuietly(socket);
      // 回调获得了新的Connection
      eventListener.connectionAcquired(call, result);

      return result;
}

上述代码中描述流程很清楚了,先找到当前的RealConnection中正在建立Socket连接的RealConnection,然后若当前的连接通路不能够再次创建新的数据交换的连接,并且从连接池中也找不到一个健康可用的连接,就最终会调用closeQuietly(toClose)去关闭Socket,并且去创建一个新的连接路由, 与新的RealConnection进行绑定。我们来看下代码1处的流程,其中包含DNS解析的过程。简单总结下,就是找一个可复用的连接,如果没有找到,就创建一个连接并放入连接池中返回,之后拿到构造好的ExchageCodec。

RouteSelector#resetNextInetSocketAddress(Proxy proxy)

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
  // 域名解析后的存放IP的集合
  inetSocketAddresses = new ArrayList<>();
  String socketHost;
  int socketPort;
  if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
    // 获取 host 与 port
    socketHost = address.url().host();
    socketPort = address.url().port();
  } else {
    SocketAddress proxyAddress = proxy.address();
    if (!(proxyAddress instanceof InetSocketAddress)) {
      throw new IllegalArgumentException(
          "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
    }
    InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
    socketHost = getHostString(proxySocketAddress);
    socketPort = proxySocketAddress.getPort();
  }
  if (proxy.type() == Proxy.Type.SOCKS) {
    // 若是WebSocket,则跳过DNS解析
    inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
  } else {
    // Event dns回调 开始解析DNS
    eventListener.dnsStart(call, socketHost);
    // Try each address for best behavior in mixed IPv4/IPv6 environments.
    // 默认使用底层Java提供的解析DNS 实现类 Inet6AddressImpl
    List<InetAddress> addresses = address.dns().lookup(socketHost);
    if (addresses.isEmpty()) {
      throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
    }
     // Event dns回调 解析DNS完成 
    eventListener.dnsEnd(call, socketHost, addresses);
    for (int i = 0, size = addresses.size(); i < size; i++) {
      InetAddress inetAddress = addresses.get(i);
      inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
    }
  }
}

Transmitter

private @Nullable IOException maybeReleaseConnection(@Nullable IOException e, boolean force) {
  Socket socket;
  Connection releasedConnection;
  boolean callEnd;
  synchronized (connectionPool) {
    if (force && exchange != null) {
      throw new IllegalStateException("cannot release connection while it is in use");
    }
    releasedConnection = this.connection;
    socket = this.connection != null && exchange == null && (force || noMoreExchanges)
        ? releaseConnectionNoEvents()
        : null;
    if (this.connection != null) releasedConnection = null;
    callEnd = noMoreExchanges && exchange == null;
  }
  closeQuietly(socket);
  if (releasedConnection != null) {
    eventListener.connectionReleased(call, releasedConnection);
  }
  if (callEnd) {
    boolean callFailed = (e != null);
    e = timeoutExit(e);
    if (callFailed) {
      eventListener.callFailed(call, e);
    } else {
      eventListener.callEnd(call);
    }
  }
  return e;
}

未完待续....

相关文章

网友评论

      本文标题:Android OkHttp根据EventListener回调顺

      本文链接:https://www.haomeiwen.com/subject/iktgsktx.html