OkHttp3地址解析之一个url点燃的繁华世界(2/2)

592 阅读11分钟

分析每个类的源码,从源码的角度解析类的功能组成及类与类之间的关系。

HttpUrl

这个类说简单也简单,就是把一个字符串url拆解出各部分。

注释里提出一个问题就是:为什么java里已经有了java.net.URL和java.net.URI,却还要再自定义一个HttpUrl呢,主要是因为这两个api的类都有问题。

首先,对java.net.URL:

下面这两个url是相等的,因为他们有相同的IP地址。

http://square.github.io/
http://google.github.io/

这是一个相当不好的设计,直接导致Url无法在Map和Set中使用。除此之外,这还会导致低效。因为这种相等性会导致Dns查询,而且还会出现错误。

对于java.net.URI:

又会出现另一种问题,下面的两个url应该是相等的,但是URI认为是两个不同的url

http://host:80/
http://host

而HttpUrl解决了上面两种问题。

HttpUrl也提供了向两个类的转换方法:

url():返回一个java.net.URL

/** Returns this URL as a {@link URL java.net.URL}. */
public URL url() {
  try {
    return new URL(url);
  } catch (MalformedURLException e) {
    throw new RuntimeException(e); // Unexpected!
  }
}

uri():返回一个URI,相当于是一个HttpUrl的严格模式

public URI uri() {
  String uri = newBuilder().reencodeForUri().toString();
  try {
    return new URI(uri);
  } catch (URISyntaxException e) {
    // Unlikely edge case: the URI has a forbidden character in the fragment. Strip it & retry.
    try {
      String stripped = uri.replaceAll("[\\u0000-\\u001F\\u007F-\\u009F\\p{javaWhitespace}]", "");
      return URI.create(stripped);
    } catch (Exception e1) {
      throw new RuntimeException(e); // Unexpected!
    }
  }
}

上面HttpUrl提出了对url相等性的讨论,所以它肯定会重写equals()和hashCode()两个方法

equals():

@Override public boolean equals(@Nullable Object other) {
  return other instanceof HttpUrl && ((HttpUrl) other).url.equals(url);
}

hashCode():就是直接取得成员变量url字符串的hashcode。也就是说,HttpUrl可以向字符串一样在Map和Set中使用

@Override public int hashCode() {
  return url.hashCode();
}

现在,我们来看看url变量的来历。

url来自于其建造者Builder的toString()方法:this.url = builder.toString();

看一下Builder的toString()方法,出乎意料,builder类中并没有对应的url成员,它是直接根据其他各种参数拼装出来的一个url,设置给了HttpUrl的url字段。

以下代码,仅供欣赏。

@Override public String toString() {
  StringBuilder result = new StringBuilder();
  result.append(scheme);
  result.append("://");

  if (!encodedUsername.isEmpty() || !encodedPassword.isEmpty()) {
    result.append(encodedUsername);
    if (!encodedPassword.isEmpty()) {
      result.append(':');
      result.append(encodedPassword);
    }
    result.append('@');
  }

  if (host.indexOf(':') != -1) {
    // Host is an IPv6 address.
    result.append('[');
    result.append(host);
    result.append(']');
  } else {
    result.append(host);
  }

  int effectivePort = effectivePort();
  if (effectivePort != defaultPort(scheme)) {
    result.append(':');
    result.append(effectivePort);
  }

  pathSegmentsToString(result, encodedPathSegments);

  if (encodedQueryNamesAndValues != null) {
    result.append('?');
    namesAndValuesToQueryString(result, encodedQueryNamesAndValues);
  }

  if (encodedFragment != null) {
    result.append('#');
    result.append(encodedFragment);
  }

  return result.toString();
}

HttpUrl是面向 url 字符串的

Address

与源服务器的链接规范。

1、对于简单的链接,Address代表的就是服务器的主机名host和端口port。

2、如果需要代理,那Address还包括了代理proxy相关的信息。

3、对于安全的链接,Address还会包含SSL socket factory, hostname verifier, 和 certificate pinner.。

也就是说,Address是面向链接的。

成员变量

public final class Address {
  final HttpUrl url;
  final Dns dns;
  final SocketFactory socketFactory;
  final Authenticator proxyAuthenticator;
  final List protocols;
  final List connectionSpecs;
  final ProxySelector proxySelector;
  final @Nullable Proxy proxy;
  final @Nullable SSLSocketFactory sslSocketFactory;
  final @Nullable HostnameVerifier hostnameVerifier;
  final @Nullable CertificatePinner certificatePinner;

都是一些参数配置类型的信息。

构造方法:

public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
    @Nullable SSLSocketFactory sslSocketFactory, @Nullable HostnameVerifier hostnameVerifier,
    @Nullable CertificatePinner certificatePinner, Authenticator proxyAuthenticator,
    @Nullable Proxy proxy, List protocols, List connectionSpecs,
    ProxySelector proxySelector) {
  this.url = new HttpUrl.Builder()//创建成员HttpUrl
      .scheme(sslSocketFactory != null ? "https" : "http") //可以看到,有ssl,就是https。没有就是http
      .host(uriHost)
      .port(uriPort)
      .build();

  if (dns == null) throw new NullPointerException("dns == null"); //必要信息,dns不能为空
  this.dns = dns;

  if (socketFactory == null) throw new NullPointerException("socketFactory == null");//必要信息,socketFactory 不能为空
  this.socketFactory = socketFactory;

  if (proxyAuthenticator == null) {
    throw new NullPointerException("proxyAuthenticator == null"); 
  }
  this.proxyAuthenticator = proxyAuthenticator;

  if (protocols == null) throw new NullPointerException("protocols == null");
  this.protocols = Util.immutableList(protocols);

  if (connectionSpecs == null) throw new NullPointerException("connectionSpecs == null");
  this.connectionSpecs = Util.immutableList(connectionSpecs);

  if (proxySelector == null) throw new NullPointerException("proxySelector == null");
  this.proxySelector = proxySelector;
//以上是必要信息,均不能为空
  this.proxy = proxy;
  this.sslSocketFactory = sslSocketFactory;
  this.hostnameVerifier = hostnameVerifier;
  this.certificatePinner = certificatePinner;
}

equals(@Nullable Object other):两个Address怎样是相等的呢

@Override public boolean equals(@Nullable Object other) {
  return other instanceof Address
      && url.equals(((Address) other).url)//首先要比较两个的url是否相等
      && equalsNonHost((Address) other);//最后比较address自身的一些属性是否相等
}

equalsNonHost(Address that):比较两个address自身的一些属性是否相等

除了个成员变量必须要equals之外,dns需要相等。另外,url的端口号port必须也要相等。

boolean equalsNonHost(Address that) {
  return this.dns.equals(that.dns)//dns相等
      && this.proxyAuthenticator.equals(that.proxyAuthenticator)
      && this.protocols.equals(that.protocols)
      && this.connectionSpecs.equals(that.connectionSpecs)
      && this.proxySelector.equals(that.proxySelector)
      && equal(this.proxy, that.proxy)
      && equal(this.sslSocketFactory, that.sslSocketFactory)
      && equal(this.hostnameVerifier, that.hostnameVerifier)
      && equal(this.certificatePinner, that.certificatePinner)
      && this.url().port() == that.url().port();//url的端口号port必须也要相等。
}

hashCode():仅供欣赏

这一部分不懂了:

为啥result的初始值是 17 ?

又为啥每次要乘以 31 ?

因为是31 = 2*5 - 1,17 = 2*5 / 2 + 1 ?

@Override public int hashCode() {
  int result = 17;
  result = 31 * result + url.hashCode();
  result = 31 * result + dns.hashCode();
  result = 31 * result + proxyAuthenticator.hashCode();
  result = 31 * result + protocols.hashCode();
  result = 31 * result + connectionSpecs.hashCode();
  result = 31 * result + proxySelector.hashCode();
  result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
  result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
  result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
  result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0);
  return result;
}

Address是面向链接的。

Dns

Dns是一个接口

为主机名解析IP地址的域名服务。

//大部分应用都是使用默认的dns,提供自定义的dns可以处理ipV6, IPv4, 或者某些具体的ip
public interface Dns {
  /**
   * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
   * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
   */
   //这个默认的DNS通过调用InetAddress的getAllByName()来要求底层的操作系统查找IP地址。
   //大部分自定义的dns需要委托给这个实例。
  Dns SYSTEM = new Dns() {
    @Override public List lookup(String hostname) throws UnknownHostException {
      if (hostname == null) throw new UnknownHostException("hostname == null");
      try {
        return Arrays.asList(InetAddress.getAllByName(hostname));
      } catch (NullPointerException e) {
        UnknownHostException unknownHostException =
            new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
        unknownHostException.initCause(e);
        throw unknownHostException;
      }
    }
  };

  /**
   * Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp. If
   * a connection to an address fails, OkHttp will retry the connection with the next address until
   * either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded.
   */
   //根据一个主机名,返回对应的IP地址,多个IP地址会按照Okhttp要求的顺序排序。
   //如果到一个Address的链接失败了,okhttp会取下一个 address 重新尝试链接,直到链接成功,或者address都用完了,或者超时了。
  List lookup(String hostname) throws UnknownHostException;
}

Dns是面向Address。

Proxy

一个proxy代表的是一个代理设置,通常是一个type类型,和一个socket地址。

一个proxy是一个不可变的对象

成员变量:

public class Proxy {

    /**
     * Represents the proxy type.
     *
     * @since 1.5
     */
     //枚举 Type代表的是代理的类型,
     //一共三种:
    public enum Type {
        /**
         * Represents a direct connection, or the absence of a proxy.
         */
         //表示直接连接,或没有代理。
        DIRECT,
        /**
         * Represents proxy for high level protocols such as HTTP or FTP.
         */
         //表示高级协议(如HTTP或FTP)的代理。
        HTTP,
        /**
         * Represents a SOCKS (V4 or V5) proxy.
         */
         //表示SOCKS(V4或V5)代理。
        SOCKS
    };

    private Type type;//自己的类型,
    private SocketAddress sa;//socket的地址

    /**
     * A proxy setting that represents a {@code DIRECT} connection,
     * basically telling the protocol handler not to use any proxying.
     * Used, for instance, to create sockets bypassing any other global
     * proxy settings (like SOCKS):
     * * {@code Socket s = new Socket(Proxy.NO_PROXY);}
     *
     */
    public final static Proxy NO_PROXY = new Proxy();

构造方法:

默认无参构造的是一个直连无代理的链接。

当要指定代理type和socketAddress的时候,这两者需要匹配。

// Creates the proxy that represents a {@code DIRECT} connection.
private Proxy() {
    type = Type.DIRECT;
    sa = null;
}

/**
 * Creates an entry representing a PROXY connection.
 * Certain combinations are illegal. For instance, for types Http, and
 * Socks, a SocketAddress must be provided.
 * * Use the {@code Proxy.NO_PROXY} constant
 * for representing a direct connection.
 *
 * @param type the {@code Type} of the proxy
 * @param sa the {@code SocketAddress} for that proxy
 * @throws IllegalArgumentException when the type and the address are
 * incompatible
 */
public Proxy(Type type, SocketAddress sa) {
    if ((type == Type.DIRECT) || !(sa instanceof InetSocketAddress))
        throw new IllegalArgumentException("type " + type + " is not compatible with address " + sa);
    this.type = type;
    this.sa = sa;
}

equals():Proxy是如何相等的

必须type和socketAddress两者都相等才行

是允许socketAddress为空值的情况的

* Compares this object against the specified object.
 * The result is {@code true} if and only if the argument is
 * not {@code null} and it represents the same proxy as
 * this object.
 * * Two instances of {@code Proxy} represent the same
 * address if both the SocketAddresses and type are equal.
 *
 * @param   obj   the object to compare against.
 * @return  {@code true} if the objects are the same;
 *          {@code false} otherwise.
 * @see java.net.InetSocketAddress#equals(java.lang.Object)
 */
public final boolean equals(Object obj) {
    if (obj == null || !(obj instanceof Proxy))
        return false;
    Proxy p = (Proxy) obj;
    if (p.type() == type()) {
        if (address() == null) {
            return (p.address() == null);
        } else
            return address().equals(p.address());
    }
    return false;
}

hashCode():仅供欣赏

/**
 * Returns a hashcode for this Proxy.
 *
 * @return  a hash code value for this Proxy.
 */
public final int hashCode() {
    if (address() == null)
        return type().hashCode();
    return type().hashCode() + address().hashCode();
}

Proxy也是面向Address的,是为Address服务的

ProxySelector

ProxySelector是一个抽象类,实现的选择器应该注册到ProxySelector里面。

ProxySelector自身提供了一个默认的ProxySelector实例:DefaultProxySelector作为选择器。

类注释:

//当通过一个URL链接到网络资源的时候,通过ProxySelector来选择一个代理服务器。
 //一个实际的proxySelector是这个ProxySelector的子类。并且通过调用setDefault方法进行注册。
 //当然,也可以调用getDefault方法获得注册的代理选择器。

//当一个代理选择器注册后,UrlConnection的子类应该为每一个URL请求都调用select方法,
 //这样代理选择器就会确定到底是要建立一个直连的还是代理的链接
 //select方法返回的是一个迭代器

//如果一个链接无法建立到代理服务器的链接,那调用者需要调用选择器的connectFailed方法
public abstract class ProxySelector {
    /**
     * The system wide proxy selector that selects the proxy server to
     * use, if any, when connecting to a remote object referenced by
     * an URL.
     *
     * @see #setDefault(ProxySelector)
     */
    private static ProxySelector theProxySelector;

    static {
        try {
            Class c = Class.forName("sun.net.spi.DefaultProxySelector");
            if (c != null && ProxySelector.class.isAssignableFrom(c)) {
                theProxySelector = (ProxySelector) c.newInstance();//通过动态加载类的方式创建
            }
        } catch (Exception e) {
            theProxySelector = null;
        }
    }
 
 

select(URI uri):如何选的我不知道,我想知道返回的是什么

public abstract List select(URI uri);

总之就是基于一切可基于的参数,选择出一个proxy代理列表,当没有代理可用的时候,这个方法也会返回一个列表,只不过里面只有一个元素,那就是代表直连的Proxy.NO_PROXY

很显然, ProxySelector面向Proxy

需要attention一下,ProxySelector和Proxy都是Java自己的api,它们不是Okhttp滴。

Route

路线。

链接的建立需要两部分一个是IP address,一个是代理proxy。IP是链接的目的地,Proxy表示如何到达目的地。

而一个Route,则表示决策后的一条可选的路线:通过方式M,到达目的地N。

成员变量:

public final class Route {
  final Address address;
  final Proxy proxy;
  final InetSocketAddress inetSocketAddress;

根据注释的推测,本以为会有两个成员,没想到多出来了一个InetSocketAddress。

关于InetSocketAddress:

InetSocketAddress是SocketAddress的实现子类。

此类实现 IP 套接字地址(IP 地址 + 端口号),不依赖任何协议。

在使用Socket来连接服务器时最简单的方式就是直接使用IP和端口,但Socket类中的connect方法并未提供这种方式,而是使用SocketAddress类来向connect方法传递服务器的IP和端口。

SocketAddress只是个抽象类,它除了有一个默认的构造方法外,其它的方法都是abstract的,因此,我们必须使用SocketAddress的子类来建立SocketAddress对象,也就是唯一的子类InetSocketAddress

构造方法:

唯一的调用点就RouteSelector中的next方法。

public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress) {
  if (address == null) {
    throw new NullPointerException("address == null");
  }
  if (proxy == null) {
    throw new NullPointerException("proxy == null");
  }
  if (inetSocketAddress == null) {
    throw new NullPointerException("inetSocketAddress == null");
  }
  //这三个都不能是空的
  this.address = address;
  this.proxy = proxy;
  this.inetSocketAddress = inetSocketAddress;
}

requiresTunnel():请求隧道

此路由需要通过HTTP代理对HTTPS进行隧道。

/**
 * Returns true if this route tunnels HTTPS through an HTTP proxy. See RFC 2817, Section 5.2.
 */
public boolean requiresTunnel() {
    //条件就是 是Https链接,但是代理类型是Proxy.Type.HTTP
  return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
}

equals():相等

三个成员变量同时相等

@Override public boolean equals(@Nullable Object other) {
  return other instanceof Route
      && ((Route) other).address.equals(address)
      && ((Route) other).proxy.equals(proxy)
      && ((Route) other).inetSocketAddress.equals(inetSocketAddress);
}

hashCode():仅供欣赏

@Override public int hashCode() {
  int result = 17;
  result = 31 * result + address.hashCode();
  result = 31 * result + proxy.hashCode();
  result = 31 * result + inetSocketAddress.hashCode();
  return result;
}

Route是面向Address和Proxy两个的。

RouteSelector

唯一创建的地方就是StreamAllocation中:

this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);

成员变量

//选择出一串链接到源服务器的route,每个链接都需要选择一个代理服务器,IP,TLS模式。
 //链接有时还可以复用
public final class RouteSelector {
  private final Address address;
  private final RouteDatabase routeDatabase;//无用route的黑名单
  private final Call call;//监听生命周期用的
  private final EventListener eventListener;//监听生命周期用的

  /* State for negotiating the next proxy to use. */
  private List proxies = Collections.emptyList();//协商下一个要使用的代理,注意这是代理Proxy
  private int nextProxyIndex;//下一个代理Proxy的下标

  /* State for negotiating the next socket address to use. */
  private List inetSocketAddresses = Collections.emptyList();//协商下一个要是用的socket地址

  /* State for negotiating failed routes */
  private final List postponedRoutes = new ArrayList<>();//失败的route列表

构造方法:

public RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
    EventListener eventListener) {
  this.address = address;
  this.routeDatabase = routeDatabase;
  this.call = call;
  this.eventListener = eventListener;

//赋值完成后,立即准备好代理服务器
  resetNextProxy(address.url(), address.proxy());
}

resetNextProxy(HttpUrl url, Proxy proxy):准备好选择器

private void resetNextProxy(HttpUrl url, Proxy proxy) {
    //参数url和proxy均来自address
  if (proxy != null) {
    // If the user specifies a proxy, try that and only that.
    proxies = Collections.singletonList(proxy);//如果指定了一个代理,则放在列表里。
  } else {
    // Try each of the ProxySelector choices until one connection succeeds.
    //如果没有代理
    List proxiesOrNull = address.proxySelector().select(url.uri());//则通过代理选择器选出可用的代理
    proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
        ? Util.immutableList(proxiesOrNull)
        : Util.immutableList(Proxy.NO_PROXY);
  }
  nextProxyIndex = 0;
}

所以最后可用的代理列表有三种:

1、address直接提供的proxy,这时候列表只有这一项。

2、address未提供时,通过address的代理选择器得到了,这时候是整个代理选择器提供的proxy列表

3、address未提供时,通过address的代理选择器什么也没得到,这时候列表是一个Proxy.NO_PROXY

hasNext():是否还有可选择的route

/**
 * Returns true if there's another set of routes to attempt. Every address has at least one route.
 */
 //注释里提到一个前提:每一个address都至少有一个route
public boolean hasNext() {
  return hasNextProxy() || !postponedRoutes.isEmpty();
}

所以还有route的条件就是要么还有可用的proxy,要么postponedRoutes列表不为空

hasNextProxy():是否还有可尝试的代理proxy

/** Returns true if there's another proxy to try. */
private boolean hasNextProxy() {
  return nextProxyIndex < proxies.size();//没查询到列表尾部就算还有
}

next():查找下一波可用的route

public Selection next() throws IOException {
  if (!hasNext()) {
    throw new NoSuchElementException();
  }

  // Compute the next set of routes to attempt.
  List routes = new ArrayList<>();//装着可尝试的route的集合
  while (hasNextProxy()) {//遍历代理列表
    // Postponed routes are always tried last. For example, if we have 2 proxies and all the
    // routes for proxy1 should be postponed, we'll move to proxy2. Only after we've exhausted
    // all the good routes will we attempt the postponed routes.
    //推迟的路线总是最后一次尝试。之所以叫推迟,是因为这些route都是最近失败的。如果proxy1的route都是推迟的,那就优先处理proxy,最后还是不行的话,再回到proxy处理
    Proxy proxy = nextProxy();//取下一个代理,
    for (int i = 0, size = inetSocketAddresses.size(); i < size; i++) {
      Route route = new Route(address, proxy, inetSocketAddresses.get(i));//生成一个route
      if (routeDatabase.shouldPostpone(route)) {//如果route最近失败了
        postponedRoutes.add(route);//先将route加入到失败列表中
      } else {
        routes.add(route);//一个正常的route,加入到routes列表中
      }
    }

    if (!routes.isEmpty()) {//针对上面刚刚取到的那个proxy,如果得到可用的route了,则退出while,
      break;
    }
  }

  if (routes.isEmpty()) {//如果没有可用的route,那就把上面循环时取到哪些之前失败过的routeadd进来
    // We've exhausted all Proxies so fallback to the postponed routes.
    routes.addAll(postponedRoutes);
    postponedRoutes.clear();
  }

//将本次选出的route封装进内部类Selection中。
  return new Selection(routes);
}

我们知道Route是面向address和proxy的。所以一个route的创建必须要有这两者。

先得到一个代理proxy,然后循环inetSocketAddresses,根据proxy和address和inetSocketAddresses.get(i)组成一个Route。

这时候,会对新产生的Route状态进行判断,如果这个Route之前失败过,那就把它先放在失败Route列表postponedRoutes种。否则就放在正常的routes列表中。

当循环完一次postponedRoutes后,判断是否有正常的Route,如果有,则退出整个while循环。

来到vwhile外面,表示获取Route的操作已结束。这时候判断是否有可用route,如果没有, 那就把之前得到的那些失败过的route再拿来试一遍。

最后生成的可用routes被封装成内部类Selection。

nextProxy():获取下一个代理

/** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
private Proxy nextProxy() throws IOException {
  if (!hasNextProxy()) {
    throw new SocketException("No route to " + address.url().host()
        + "; exhausted proxy configurations: " + proxies);//没有代理可用,直接无法创建了链接。抛出Socket异常
  }
  Proxy result = proxies.get(nextProxyIndex++);//从列表中取下一个
  resetNextInetSocketAddress(result);//为当前代理或主机准备要尝试的套接字地址。
  return result;
}

resetNextInetSocketAddress(Proxy proxy):为当前代理或主机准备要尝试的套接字地址。

/** Prepares the socket addresses to attempt for the current proxy or host. */
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
  // Clear the addresses. Necessary if getAllByName() below throws!
  inetSocketAddresses = new ArrayList<>();

  String socketHost;//主机名
  int socketPort;端口
  if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      //如果proxy是直接或者socks,取address的主机和端口
    socketHost = address.url().host();
    socketPort = address.url().port();
  } else {
      //如果代理类型是http
      //取代理的address中的主机和端口
    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 (socketPort < 1 || socketPort > 65535) {
    throw new SocketException("No route to " + socketHost + ":" + socketPort
        + "; port is out of range");
  }

  if (proxy.type() == Proxy.Type.SOCKS) {
    inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
  } else {
    eventListener.dnsStart(call, socketHost);

    // Try each address for best behavior in mixed IPv4/IPv6 environments.
    List addresses = address.dns().lookup(socketHost);//启动dns
    if (addresses.isEmpty()) {
      throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
    }

    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));
    }
  }
}

根据代理的类型为,取不同的主机名和端口号,生成下一步可用的inetSocketAddresses。这个过程中涉及到了DNS

List addresses = address.dns().lookup(socketHost);//启动dns

内部类:Selection : 就是一个可用route的列表

封装了对routes列表的取值,不用每一个用到的地方都自己去循环取值了。

/** A set of selected Routes. */
public static final class Selection {
  private final List routes;
  private int nextRouteIndex = 0;

  Selection(List routes) {
    this.routes = routes;
  }

  public boolean hasNext() {
    return nextRouteIndex < routes.size();
  }

  public Route next() {
    if (!hasNext()) {
      throw new NoSuchElementException();
    }
    return routes.get(nextRouteIndex++);
  }

  public List getAll() {
    return new ArrayList<>(routes);
  }
}

RouteSelector 是面向Route的

RouteDatabase

一个黑名单

/**
 * A blacklist of failed routes to avoid when creating a new connection to a target address. This is
 * used so that OkHttp can learn from its mistakes: if there was a failure attempting to connect to
 * a specific IP address or proxy server, that failure is remembered and alternate routes are
 * preferred.
 */
public final class RouteDatabase {
  private final Set failedRoutes = new LinkedHashSet<>();

  /** Records a failure connecting to {@code failedRoute}. */
  public synchronized void failed(Route failedRoute) {
    failedRoutes.add(failedRoute);
  }

  /** Records success connecting to {@code route}. */
  public synchronized void connected(Route route) {
    failedRoutes.remove(route);
  }

  /** Returns true if {@code route} has failed recently and should be avoided. */
  public synchronized boolean shouldPostpone(Route route) {
    return failedRoutes.contains(route);
  }
}