HttpClient/HttpURLConnection + H

2017-04-23  本文已影响619人  李牙刷儿

在Android端如果OkHttp作为网络请求框架,由于其提供了自定义DNS服务接口,可以很优雅地结合HttpDns,相关实现可参考:HttpDns+OkHttp最佳实践
如果您使用HttpClientHttpURLConnection发起网络请求,尽管无法直接自定义Dns服务,但是由于HttpClientHttpURLConnection也通过InetAddress进行域名解析,通过修改InetAddress的DNS缓存,同样可以比通用方案更为优雅地使用HttpDns。

InetAddress在虚拟机层面提供了域名解析能力,通过调用InetAddress.getByName(String host)即可获取域名对应的IP。调用InetAddress.getByName(String host)时,InetAddress会首先检查本地是否保存有对应域名的ip缓存,如果有且未过期则直接返回;如果没有则调用系统DNS服务(Android的DNS也是采用NetBSD-derived resolver library来实现)获取相应域名的IP,并在写入本地缓存后返回该IP。

核心代码位于java.net.InetAddress.lookupHostByName(String host, int netId)

public class InetAddress implements Serializable {
  ...
      /**
     * Resolves a hostname to its IP addresses using a cache.
     *
     * @param host the hostname to resolve.
     * @param netId the network to perform resolution upon.
     * @return the IP addresses of the host.
     */
    private static InetAddress[] lookupHostByName(String host, int netId)
            throws UnknownHostException {
        BlockGuard.getThreadPolicy().onNetwork();
        // Do we have a result cached?
        Object cachedResult = addressCache.get(host, netId);
        if (cachedResult != null) {
            if (cachedResult instanceof InetAddress[]) {
                // A cached positive result.
                return (InetAddress[]) cachedResult;
            } else {
                // A cached negative result.
                throw new UnknownHostException((String) cachedResult);
            }
        }
        try {
            StructAddrinfo hints = new StructAddrinfo();
            hints.ai_flags = AI_ADDRCONFIG;
            hints.ai_family = AF_UNSPEC;
            // If we don't specify a socket type, every address will appear twice, once
            // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family
            // anyway, just pick one.
            hints.ai_socktype = SOCK_STREAM;
            InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId);
            // TODO: should getaddrinfo set the hostname of the InetAddresses it returns?
            for (InetAddress address : addresses) {
                address.hostName = host;
            }
            addressCache.put(host, netId, addresses);
            return addresses;
        } catch (GaiException gaiException) {
          ...
        }
    }
}

其中addressCacheInetAddress的本地缓存:

private static final AddressCache addressCache = new AddressCache();

结合InetAddress的解析策略,我们可以通过如下方法实现自定义DNS服务:

具体实现可参考以下代码:

public class CustomDns {

    public static void writeSystemDnsCache(String hostName, String ip) {
        try {
            Class inetAddressClass = InetAddress.class;
            Field field = inetAddressClass.getDeclaredField("addressCache");
            field.setAccessible(true);
            Object object = field.get(inetAddressClass);
            Class cacheClass = object.getClass();
            Method putMethod;
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                //put方法在api21及以上为put(String host, int netId, InetAddress[] address)
                putMethod = cacheClass.getDeclaredMethod("put", String.class, int.class, InetAddress[].class);
            } else {
                //put方法在api20及以下为put(String host, InetAddress[] address)
                putMethod = cacheClass.getDeclaredMethod("put", String.class, InetAddress[].class);
            }
            putMethod.setAccessible(true);
            String[] ipStr = ip.split("\\.");
            byte[] ipBuf = new byte[4];
            for(int i = 0; i < 4; i++) {
                ipBuf[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
            }
            if(Build.VERSION.SDK_INT  >= Build.VERSION_CODES.LOLLIPOP) {
                putMethod.invoke(object, hostName, 0, new InetAddress[] {InetAddress.getByAddress(ipBuf)});
            } else {
                putMethod.invoke(object, hostName, new InetAddress[] {InetAddress.getByAddress(ipBuf)});
            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

和通用方案相比,使用该方法具有下列优势:

<font color="red">另外使用该方案请务必注意以下几点:</font>

上一篇 下一篇

猜你喜欢

热点阅读