Android 4.4.4 发展到 Andoird 5.0.0 其中 OKhttp 中的一点点变化,导致的一个问题:
在使用PRoxySelector 中的select函数返回proxy ,构造proxy如果使用的resolve的方式构造,
在而后的网络请求中会经历如下过程:
1、host 解析成 InetAddress
2、InetAddress反向解析成name
3、然后将name正向解析成InetAddress
过程及其繁琐,导致请求的效率下降,也可能导致错误。
什么样的错误呢?
例如先给ip:
1、1.2.3.4,
2、逆向解析成 :4.3.2.1.broad.wh.hb.dynamic.163data.com.cn
3、正向解析出错 public1.114dns.com can't find 4.3.2.1.broad.wh.hb.dynamic.163data.com.cn: Non-existent domain
仔细分析原因后发现,Android 5.0.0以下的网络请求使用OKhttp,然后里面有一个比较关键的类RouteSelector。其中有一段代码是这样的:
private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
if (proxy.type() == Proxy.Type.DIRECT) {
socketHost = uri.getHost();
socketPort = getEffectivePort(uri);
SocketAddress proxyAddress = proxy.address();
if (!(proxyAddress instanceof InetSocketAddress)) {
throw new IllegalArgumentException(
"Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
socketHost = proxySocketAddress.getHostName();
socketPort = proxySocketAddress.getPort();
// Try each address for best behavior in mixed IPv4/IPv6 environments.
socketAddresses = dns.getAllByName(socketHost);
nextSocketAddressIndex = 0;
public
class
InetAddress
implements
Serializable {
private
static
InetAddress getHostByAddrImpl(InetAddress address)
throws
UnknownHostException {
BlockGuard.getThreadPolicy().onNetwork();
try
{
String hostname = Libcore.os.getnameinfo(address, NI_NAMEREQD);//
如果找不到主机名字, 将其作为一个错误对待.
return
makeInetAddress(address.
ipaddress
.clone(), hostname);
}
catch
(GaiException gaiException) {
throw
gaiException.rethrowAsUnknownHostException();
}
}
public
String getHostName() {
if
(
hostName
==
null
) {
try
{
hostName= getHostByAddrImpl(this).hostName;
}
catch
(UnknownHostException ex) {
hostName
= getHostAddress();
}
}
return
hostName
;
}
/**
* Returns the numeric representation of this IP address (such as "127.0.0.1").
*/
public
String getHostAddress() {
return
Libcore.os.getnameinfo(
this
, NI_NUMERICHOST);
// Can't throw.以数字形式而非名字返回主机地址
}
}
- NI_DGRAM: 服务基于数据报而非基于流.
- NI_NAMEREQD: 如果找不到主机名字, 将其作为一个错误对待.
- NI_NOFQDN: 对于本地主机, 仅返回完全限定域名的节点名部分.
- NI_NUMERICHOST: 以数字形式而非名字返回主机地址.
- NI_NUMERICSERV: 以数字形式而非名字返回服务地址(即端口号).
其中代码段:
socketHost = proxySocketAddress.getHostName();
获取到的结果会是一个通过逆向解析获取ip对应的域名,如果找不到主机名字, 将其作为一个异常抛出.然后使用IP作为host。
按照正常逻辑 从IP反向解析域名能通的话,应该这个域名也能够解析成IP!!!
但是现实生活中不是这样的,可能我们活在中国奇葩的网络中吧。
还好在Andorid 5.0.0以后版本中的OKhttp使用已经切换如下代码了:
private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
// Clear the addresses. Necessary if getAllByName() below throws!
inetSocketAddresses = new ArrayList<>();
if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
socketHost = address.getUriHost();
socketPort = getEffectivePort(uri);
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();
// Try each address for best behavior in mixed IPv4/IPv6 environments.
for (InetAddress inetAddress : network.resolveInetAddresses(socketHost)) {
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
nextInetSocketAddressIndex = 0;
Obtain a "host" from an
java.net.InetSocketAddress
. This returns a string containing either an actual host name or a numeric IP address.
static String getHostString(InetSocketAddress socketAddress) {
InetAddress address = socketAddress.getAddress();
// The InetSocketAddress was specified with a string (either a numeric IP or a host name). If
// it is a name, all IPs for that name should be tried. If it is an IP address, only that IP
// address should be tried.
return socketAddress.getHostName();
// The InetSocketAddress has a specific address: we should only try that address. Therefore we
// return the address and ignore any host name that may be available.
return address.getHostAddress();
这样就能很好的解决这个问题,不存在什么反向解析 然后正向解析的问题了。但是我们Android5.0一下的操作系统怎么办呢????
哈哈 Android 源代码管理的官网上已经将okhttp这个bug 同步过去,bug 号码是#1186
https://github.com/square/okhttp/pull/1186
其实我们创建代理的时候使用不解析的方式创建就可以避免上面的问题!!
https://android.googlesource.com/platform/external/okhttp/+log/android-5.1.0_r1/
https://android.googlesource.com/platform/external/okhttp/+log/android-5.1.0_r1/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java
android / platform / external / okhttp / android-5.1.0_r1 / okhttp / src / main / java / com / squareup / okhttp / internal / http /
RouteSelector.java
tag |
d3c92892dd20b7362fe5039f99a0c49304425e30 |
|
tagger |
The Android Open Source Project |
Mon Mar 02 08:26:28 2015 -0800 |
object |
be40c8024c0572878c55d2e032f3cbb6ef85ccd9 |
|
Android 5.1.0 release 1
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iEYEABECAAYFAlT0jzQACgkQ6K0/gZqxDnhW0ACeIsx37SoxX66T/Vx/uThDRCLQ
zCQAoJE9d0p9nbad8JBPOEC8OJ/32quz
=aQm/
-----END PGP SIGNATURE-----
- 2ed0095 Avoid a reverse DNS-lookup for a numeric proxy address by Neil Fuller · 1 year ago
- cc168fe Be consistent about host names in RouteSelector. by Narayan Kamath · 1 year, 2 months ago
- 7c7f22d Allow callers to pass in a custom host resolver implementation. by Lorenzo Colitti · 1 year, 5 months ago
- 3c938a3 Update okhttp to a more recent commit. by Neil Fuller · 1 year, 10 months ago
- 166772b Update okhttp. by Narayan Kamath · 2 years, 1 month ago
https://github.com/square/okhttp/pull/1186