全部产品

Android端HTTPDNS+OkHttp接入指南

注意

当前最佳实践文档只针对结合使用时,如何使用HTTPDNS解析出的IP,关于HTTPDNS本身的解析服务,请先查看Android SDK开发指南

背景信息

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服务:

public class OkHttpDns implements Dns {
    private static final Dns SYSTEM = Dns.SYSTEM;
    HttpDnsService httpdns;//httpdns 解析服务
    private static OkHttpDns instance = null;
    private OkHttpDns(Context context) {
        this.httpdns = HttpDns.getService(context, "account id");
    }
    public static OkHttpDns getInstance(Context context) {
        if(instance == null) {
            instance = new OkHttpDns(context);
        }
        return instance;
    }
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException {
        //通过异步解析接口获取ip
        String ip = httpdns.getIpByHostAsync(hostname);
        if(ip != null) {
            //如果ip不为null,直接使用该ip进行网络请求
            List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip));
            Log.e("OkHttpDns", "inetAddresses:" + inetAddresses);
            return inetAddresses;
        }
        //如果返回null,走系统DNS服务解析域名
        return Dns.SYSTEM.lookup(hostname);
    }
}

Dns接口附加说明:

Dns接口返回的IP,在okhttp进行网络请求时,受OkHttpClient的connectTimeout()影响,具体为: 1、举例:如果OkHttpClient的connectTimeout()设置为5s,即尝试连接的超时时间为5s,如果发生连接超时,同一IP会尝试进行两次连接,即同一IP的超时总时间是10s。

2、Dns接口,lookup()方法中,return的List<InetAddress>如果为多个IP,会顺序使用这些IP进行连接。

  • 如果第一个IP建连成功,则直接完成请求。

  • 如果第一个IP不可用,结合上述第一点,会使用第一个IP尝试连接两次,即两次超时时间后,顺序使用下一个IP尝试连接。以此类推。

综合两点所述,在Dns接口,lookup()方法中,可以return包含多个IP的List<InetAddress>,可以有效避免返回单一IP,而这个IP不可用导致该次请求完全失败的场景。需要注意超时时间的设置,可以稍微给短一点。

创建OkHttpClient

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

private void okhttpDnsRequest() {
    OkHttpClient client = new OkHttpClient.Builder()
    .dns(OkHttpDns.getInstance(getApplicationContext()))
    .build();

    Request request = new Request.Builder()
    .url("http://www.aliyun.com")
    .build();

    Response response = null;
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            e.printStackTrace();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
                DataInputStream dis = new DataInputStream(response.body().byteStream());
                int len;
                byte[] buff = new byte[4096];
                StringBuilder result = new StringBuilder();
                while ((len = dis.read(buff)) != -1) {
                    result.append(new String(buff, 0, len));
                }
                Log.d("OkHttpDns", "Response: " + result.toString());
            }
        });
}

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

总结

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

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

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

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