前言
本文转自feshfans的博客 Android 在 4G 下访问 IPV6 慢的解决方案,仅用于记录解决方式,如有侵权,烦请告知。
起因
因公司iOS上架应用必须要使用ipv6协议,服务端这边新增了ipv6协议支持。
Android刚开始在wifi网络环境下访问一切正常,后来在切换到4G网络状态下时,发下每次请求的时间相对之前都延长了好多,经过搜索参阅上文后,发现是每次解析都会收到两个ip地址(ipv6、ipv4),ipv6总是排在前面,会被优先使用ipv6地址访问解析,所以决定采用文中方案,使用自定义DNS方式将ipv4优先级放在第一位,杜绝此类问题的发生。
验证方式
使用下面的代码,验证DNS解析到的IP地址:
try {
InetAddress[] inetAddresses = InetAddress.getAllByName("server.xxxx.cn");
for (InetAddress inetAddress : inetAddresses) {
Log.d(TAG, inetAddress.getHostAddress());
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
解决方案
通过上面的验证,基本断定为 4G 网络下,Android 端通过 ipv6 连接的服务地址。客户端的Retrofit是基于okHttp3为核心实现的网络请求 ,在查看 javadoc 后,发现其提供了 DNS 接口,代码如下:
/**
* A domain name service that resolves IP addresses for host names. Most applications will use the
* {@linkplain #SYSTEM system DNS service}, which is the default. Some applications may provide
* their own implementation to use a different DNS server, to prefer IPv6 addresses, to prefer IPv4
* addresses, or to force a specific known IP address.
*
* <p>Implementations of this interface must be safe for concurrent use.
*/
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 SYSTEM = hostname -> {
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.
*/
List<InetAddress> lookup(String hostname) throws UnknownHostException;
}
下面我们通过实现此接口,将解析到的 ip 顺序调整一下,如果是 ipv4 则将其放到数据的第一个,其它保持不变,如下图:
package com.dohenes.common.data.remote;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import okhttp3.Dns;
import retrofit2.internal.EverythingIsNonNull;
/**
* ClassName Ipv4Dns
* Describe TODO<ipv4访问优先的DNS--解决ipv6环境下,4G网络访问太慢的问题>
* Author zihao
* Date 2020/2/27 14:59
* Version v1.0
*/
@EverythingIsNonNull
public class Ipv4PriorityDns implements Dns {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
try {
// 获取指定主机-hostname的IP地址数组(Android解析指定域名获取到的IP)
List<InetAddress> inetAddressList = new ArrayList<>();
InetAddress[] inetAddresses = InetAddress.getAllByName(hostname);
// 遍历获取到的地址,并将得到的ip顺序调整一下,使ipv4放在第一位(即优先使用ipv4协议访问)
for (InetAddress inetAddress : inetAddresses) {
if (inetAddress instanceof Inet4Address) {
inetAddressList.add(0, inetAddress);
} else {
inetAddressList.add(inetAddress);
}
}
return inetAddressList;
} catch (NullPointerException exception) {
UnknownHostException unknownHostException = new
UnknownHostException("Broken system behavior");
unknownHostException.initCause(exception);
throw unknownHostException;
}
}
}
然后设置Retrofit的OkHttpClient,修改其DNS解析类:
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
okHttpClientBuilder.dns(new MyDns());
OkHttpClient okHttpClient = okHttpClientBuilder.build();