Android端HTTPDNS+OkHttp接入指南

重要

当前最佳实践文档只针对结合使用时,如何使用HTTPDNS解析出的IP,关于HTTPDNS的接入步骤,请先查看Android SDK接入

背景信息

Android端HTTPS(含SNI)业务场景:IP直连方案一文介绍了利用HTTPDNS解析获得IP后进行IP直连的通用方法。

如果您在Android端使用的网络框架是OkHttp,通过调用OkHttp提供的自定义DNS服务接口,可以更为优雅地使用HTTPDNS的服务。

OkHttp是一个处理网络请求的开源项目,是Android端最火热的轻量级框架,由移动支付Square公司贡献用于替代HttpUrlConnection和Apache HttpClient。随着OkHttp的不断成熟,越来越多的Android开发者使用OkHttp作为网络框架。

OkHttp默认使用系统DNS服务InetAddress进行域名解析,同时也暴露了自定义DNS服务的接口,通过该接口我们可以优雅地使用HTTPDNS。

自定义DNS接口

OkHttp暴露了一个DNS接口,通过实现该接口,我们可以自定义DNS服务:

class OkHttpDns constructor(context: Context): Dns {
    private var mContext: Context

    init {
        mContext = context
    }
    @Throws(UnknownHostException::class)
    override fun lookup(host: String): List<InetAddress> {
        val httpdnsResult: HTTPDNSResult = HttpDns.getService(mContext, accountID)
            .getHttpDnsResultForHostSync(host, RequestIpType.both)
        val inetAddresses: MutableList<InetAddress> = ArrayList()
        var address: InetAddress
        try {
            if (httpdnsResult.ips != null) {
                //处理IPv4地址
                for (ipv4 in httpdnsResult.ips) {
                    address = InetAddress.getByName(ipv4)
                    inetAddresses.add(address)
                }
            }
            if (httpdnsResult.ipv6s != null) {
                //处理IPv6地址
                for (ipv6 in httpdnsResult.ipv6s) {
                    address = InetAddress.getByName(ipv6)
                    inetAddresses.add(address)
                }
            }
        } catch (e: UnknownHostException) {
        }
        return if (!inetAddresses.isEmpty()) {
            inetAddresses
        } else Dns.SYSTEM.lookup(host)
    }
}
public class OkHttpDns implements Dns {
    private Context mContext;
    public OkHttpDns(Context context) {
        mContext = context;
    }
    @Override
    public List<InetAddress> lookup(String host) throws UnknownHostException {
        HTTPDNSResult httpdnsResult = HttpDns.getService(mContext, accountID).getHttpDnsResultForHostSync(host, RequestIpType.both);
        List<InetAddress> inetAddresses = new ArrayList<>();
        InetAddress address;
        try {
            if (httpdnsResult.getIps() != null) {
                //处理IPv4地址
                for (String ipv4 : httpdnsResult.getIps()) {
                    address = InetAddress.getByName(ipv4);
                    inetAddresses.add(address);
                }
            }

            if (httpdnsResult.getIpv6s() != null) {
                //处理IPv6地址
                for (String ipv6 : httpdnsResult.getIpv6s()) {
                    address = InetAddress.getByName(ipv6);
                    inetAddresses.add(address);
                }
            }
        } catch (UnknownHostException e) {

        }

        if (!inetAddresses.isEmpty()) {
            return inetAddresses;
        }

        return okhttp3.Dns.SYSTEM.lookup(host);
    }
}

由于okhttp内部有重试机制,重试时可能会选择不同的IP建立连接,所以在Dns接口的lookup()方法中,可以返回包含多个IP的List<InetAddress>,可以有效避免返回单一IP,而这个IP不可用导致该次请求完全失败的情况。

配置OkHttpClient

创建OkHttpClient对象,传入OkHttpDns对象代替默认DNS服务:

private fun initOkHttp(context: Context) {
    val client = OkHttpClient.Builder() 
        //okhttp其他初始化配置请根据需求进行配置
        .dns(OkHttpDns(context))
        .build()
}
private void initOkHttp(Context context) {
    OkHttpClient client = new OkHttpClient.Builder()
    //okhttp其他初始化配置请根据需求进行配置
    .dns(new OkHttpDns(context))
    .build();
}

以上就是OkHttp+HttpDns实现的全部代码。

总结

相比于通用方案,OkHttp+HttpDns有以下两个主要优势:

  • 实现简单,只需通过实现DNS接口即可接入HttpDns服务。

  • 通用性强,该方案在HTTPS,SNI以及设置Cookie等场景均适用。规避了证书校验,域名检查等环节

该实践对于Retrofit+OkHttp同样适用,将配置好的OkHttpClient作为Retrofit.Builder::client(OkHttpClient)参数传入即可。