DNS(Domain Name System,域名系统),dns用于将域名解析解析为ip地址。css
例如:给你www.baidu.com的主机名,你给
我查出对应的ip地址:163.177.151.109。一些主机名还会有别名,如www.baidu.com就
有别名www.a.shifen.com,甚至不止一个别名,或一个别名有2个ip地址。在linux机子
上,运行nslookup(name service lookup)就是进行域名解析。以下面:java
~$ nslookup www.baidu.com
Server: 127.0.0.1 Address: 127.0.0.1#53 Non-authoritative answer: www.baidu.com canonical name = www.a.shifen.com. Name: www.a.shifen.com Address: 163.177.151.109 Name: www.a.shifen.com Address: 163.177.151.110
DNS工做方式分为递归查询和迭代查询,具体可参考下图linux
DNS还能够用于负载均衡、域名污染、防火墙,这些不在这里讨论。算法
所谓DNS缓存有两种,好比主从同步缓存和本地缓存,这里对于手机来讲,重点是本地DNS缓存。Android基于Linux系统,对于Android App来讲,这个缓存又多了java层。缓存
2.1 使用场景安全
固然,咱们须要明白在Android App中那些场景须要进行,这才是最重要的,有时候其实并无必要去更新缓存。总结一下,这里的场景无非以下几种:网络
场景一:存在多个运营商或者多个地区的分布式业务系统并发
好比互联网分布式业务系统,采起的是分区域、分运营商的方式不是业务系统。app
场景二:存在多个域名的业务系统,须要提早解析而且缓存ip负载均衡
<link rel="dns-prefetch" href="//g.alicdn.com" /> <link rel="dns-prefetch" href="//img.alicdn.com" /> <link rel="dns-prefetch" href="//tui.taobao.com" />
这是taobao网的dns-prefetch link,这一步是为了加速其余页面的dns
场景三:ip地址惟一,可是存在多个子域名高并发请求
综上所述:咱们能够理解为,当且仅当域名和ip地址的关系是“一对多”、“多对多”和“多对一”的状况下,可适当更新DNS缓存。
2.2系统版本状况说明
Android 4.3以前的TTL(Time To Live)分为正负两种有效期,正有效期为10分钟,最大缓存为120个,采用TTL算法回收。
// 默认有效DNS缓存时间(TTL). 600 seconds (10 minutes). private static final long DEFAULT_POSITIVE_TTL_NANOS = 600 * 1000000000L; // 默认无效缓存时间(TTL). 10 seconds. private static final long DEFAULT_NEGATIVE_TTL_NANOS = 10 * 1000000000L;
Android 4.3+的系统,缓存修正为2秒,最大缓存为16个,采用LRU算法和TTL算法进行回收。
private static final long TTL_NANOS = 2 * 1000000000L;
注意:以上代码参见java.net.AddressCache.java
3.一、修正缓存过时时间
在Android4.3以前,TTL能够用个System.setProperties进行设置,就能够将TTL修正为什么Android 4.3+一致的生存时间
Security.setProperty("networkaddress.cache.ttl", String.valueOf(2 * 1000000000L)); Security.setProperty("networkaddress.cache.negative.ttl", String.valueOf(2 * 1000000000L))
3.2 实现DNS-Prefetch
步骤3.1只是让缓存过时时间缩短了,必定程度上处理了Android 4.3以前系统的不足。可是,对于存在域名和ip“一对多”,“多对多”和“多对一”的分布式系统,若是出现网络切换,那么下次获“可能”取依旧比较耗时。所以,预获取dns是很是必要的。那么如何实现DNS-Prefetch呢
首先,咱们须要统一规范接口
public interface Dns { Dns SYSTEM = new Dns() { @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { if (hostname == null) throw new UnknownHostException("hostname == null"); return Arrays.asList(InetAddress.getAllByName(hostname)); } }; List<InetAddress> lookup(String hostname) throws UnknownHostException; }
实现接口
public class DnsManager implements Dns { private static DnsManager singleInstance; private final TreeSet<String> HOST_SET = new TreeSet<String>(); public static DnsManager getDefault(){ if(singleInstance==null) { synchronized (DnsManager.class) { if (singleInstance == null) { singleInstance = new DnsManager(); } } } return singleInstance; } @Override public synchronized List<InetAddress> lookup(String hostname) throws UnknownHostException { try { if(TextUtils.isEmpty(hostname) || TextUtils.isEmpty(hostname.trim())){ throw new UnknownHostException("hostname == null"); } List<InetAddress> list = Dns.SYSTEM.lookup(hostname); HOST_SET.add(hostname); return list; }catch (Exception e){ e.printStackTrace(); return null; } } public synchronized String quickLookup(String hostname) throws UnknownHostException { try { if(TextUtils.isEmpty(hostname) || TextUtils.isEmpty(hostname.trim())){ throw new UnknownHostException("hostname == null"); } final Uri uri = Uri.parse(hostname); InetAddress inetAddress = InetAddress.getByName(uri.getHost()); if(inetAddress==null) { Throw.exception("unkown host",UnknownHostException.class); } String dnsIp = inetAddress.getHostAddress(); HOST_SET.add(hostname); return dnsIp; } catch (Exception e) { e.printStackTrace(); return null; } } /** * 清除dns缓存 */ public synchronized void clearDnsCache(){ try { ReflectUtils.invokeMethodByName(InetAddress.class, "clearDnsCache"); }catch (Exception e){ e.printStackTrace(); return; } } /** * 获取主机集合 * @return */ public synchronized TreeSet<String> getHostSet() { return HOST_SET; } /** * 预加载DNS * @param hosts */ public synchronized void prefetchDns(List<String> hosts) { if(hosts==null && hosts.size()==0) return; for (String hostname:hosts ) { prefetchDns(hostname); } } /** * 预加载DNS * @param hostname */ public synchronized void prefetchDns(String hostname) { try{ InetAddress.getAllByName(hostname); }catch (Exception e){ e.printStackTrace(); return; } } }
使用时机
一般网络切换后,而且下次联网成功时,咱们prefetch时最好的时间,这里咱们须要经过Broadcast+IntentService
对于广播部分,咱们须要监听以下两个Action(这里推荐使用动态广播)
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
广播实现代码
public class NetStateChangeReceiver extends BroadcastReceiver{ private static final String TAG = NetStateChangeReceiver.class.getSimpleName(); private AtomicReference<String> pendingNetworkState = null; private AtomicReference<String> pendingSSID = null; public NetStateChangeReceiver() { pendingNetworkState = new AtomicReference<String>(); pendingSSID = new AtomicReference<>(); } @Override public void onReceive(Context context, Intent intent) { if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { NetworkType networkType = NetworkUtils.getNetworkType(context); notifyObservers(networkType); } if(shouldStartDnsUpdateService(context,intent)) { Intent cloneFilter = intent.cloneFilter(); cloneFilter.setClass(context, DnsUpdateIntentService.class); context.startService(cloneFilter); } } //网络可用而且网络切换的状况下启动IntentService更新 public boolean shouldStartDnsUpdateService(Context context,Intent intent){ if(NetworkUtils.isAvailable(context)){ NetworkType type = NetworkUtils.getNetworkType(context); if(type==null) return false ; String newState = type.toString(); String lastState = pendingNetworkState.get(); if(!TextUtils.isEmpty(lastState) && !lastState.equals(newState)) { pendingNetworkState.set(newState); return true; }else{ pendingNetworkState.set(newState); if(NetworkUtils.isWifiConnected(context)){ WifiInfo wifiInfo= intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); if(wifiInfo!=null) { String nextSSID = wifiInfo.getSSID(); String lastSSID = pendingSSID.get(); if(nextSSID!=null && nextSSID.equals(lastSSID)) { return true; } } } } }else{ pendingNetworkState.set(NetworkType.NETWORK_NO.toString()); } return false; } }
DnsUpdateIntentService代码以下
public class DnsUpdateIntentService extends IntentService { public DnsUpdateIntentService() { super(DnsUpdateIntentService.class.getName()); } @Override protected void onHandleIntent(@Nullable Intent intent) { runTask(); } private void runTask() { GFLog.d(DnsUpdateIntentService.class.getSimpleName()," startDns : 开始更新DNS "); updateDnsCache(); GFLog.d(DnsUpdateIntentService.class.getSimpleName()," endDns : DNS更新完成 "); } private void updateDnsCache() { try{ DnsManager dm = DnsManager.getDefault(); dm.clearDnsCache(); TreeSet<String> hostSet = dm.getHostSet(); List<String> hosts = new ArrayList<>(); hosts.addAll(hostSet); dm.prefetchDns(hosts); }catch (Exception e){ e.printStackTrace(); return; } } }
注意:DnsUpdateIntentService不能够注册为多进程,不然缓存没法更新
3.三、DNS防篡改与安全
Android 4.3以前的DNS可能存在被污染的可能,如修改resolv.conf文件,在Android 4.3+以后,统一使用Netd方式,安全性上有所提升。所以,对Android 4.3以前的系统,建议使用HttpDNS等方案,此外采起HTTPS的通讯方式,必定程度上几乎能够绝对避免此类问题的发生。
此外,咱们在ip与域名对应数量不大的app中,能够在App中提早创建不一样机房的域名映射也是一种放置篡改的方案。
3.四、Android底层DNS更新
Android基于linux,底层经过Libcore.so更新DNS,目前没有方式来更新Linux层面的DNS缓存。那么,咱们的DNS-Prefetch功能是否有必要呢?这个问题咱们须要明确,虽然咱们不必定能更新底层DNS,可是,能够促进底层DNS更新,相似System.gc()的做用。