问题
最近开发了一个 Android 项目,发现第一个接口返回得特别慢,后面的接口就没问题。用浏览器打开链接,访问速度很快,手机连上 Charles 抓包,访问速度也很快。打印 OkHttp 的日志发现第一个接口请求耗时100多秒,后面的接口都只有几十秒,多次实验结果都一样。
1
| 200 OK https://fundgz.1234567.com.cn/js/162605.js (100092ms, unknown-length body)
|
问题分析
通过查看 OkHttp 源码,发现 OkHttp 默认的连接超时时间是 10 秒。修改 connectTimeout 的值后测试,果然第一个接口返回的时间都是 connectTimeout 的 10 倍。
经过排查后发现是 Dns 的问题。OkHttp 中默认的 Dns 配置代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| interface Dns {
@Throws(UnknownHostException::class) fun lookup(hostname: String): List<InetAddress>
companion object {
@JvmField val SYSTEM: Dns = DnsSystem() private class DnsSystem : Dns { override fun lookup(hostname: String): List<InetAddress> { try { return InetAddress.getAllByName(hostname).toList() } catch (e: NullPointerException) { throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply { initCause(e) } } } } } }
|
加上日志打印出获取到的 IP 地址如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16d fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:167 fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16c fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16b fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:169 fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:168 fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16a fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16f fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:166 fundgz.1234567.com.cn/2409:8c60:2600:2:0:1:0:16e fundgz.1234567.com.cn/221.178.98.184 fundgz.1234567.com.cn/221.178.98.179 fundgz.1234567.com.cn/221.178.98.188 fundgz.1234567.com.cn/221.178.98.187 fundgz.1234567.com.cn/221.178.98.185 fundgz.1234567.com.cn/221.178.98.182 fundgz.1234567.com.cn/221.178.98.181 fundgz.1234567.com.cn/221.178.98.180 fundgz.1234567.com.cn/221.178.98.183 fundgz.1234567.com.cn/221.178.98.186
|
可以看到前面 10 个是 IPv6 地址,OkHttp 按顺序一个个解析,IPv6 的都解析超时了,所有请求时间刚好是 connectTimeout 的 10 倍再多一点。
解决问题
我们修改 Dns 的实现为下面的代码,优先解析 IPv4 地址,最后测试问题解决。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| val client = OkHttpClient.Builder() .dns(ApiDns()) .build()
class ApiDns : Dns { @Throws(UnknownHostException::class) override fun lookup(hostname: String): List<InetAddress> { try { return InetAddress.getAllByName(hostname).sortedBy { Inet6Address::class.java.isInstance(it) } } catch (e: NullPointerException) { throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply { initCause(e) } } } }
|
参考链接