Android DNS更新与DNS-Prefetch

一、什么是DNS

DNS(Domain Name System,域名系统),dns用于将域名解析解析为ip地址。

例如:给你www.baidu.com的主机名,你给 
我查出对应的ip地址:163.177.151.109。一些主机名还会有别名,如www.baidu.com就 
有别名www.a.shifen.com,甚至不止一个别名,或一个别名有2个ip地址。在linux机子 
上,运行nslookup(name service lookup)就是进行域名解析。如下面:

~$ 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工作方式分为递归查询和迭代查询,具体可参考下图

DNS还可以用于负载均衡、域名污染、防火墙,这些不在这里讨论。

二、DNS缓存

所谓DNS缓存有两种,比如主从同步缓存和本地缓存,这里对于手机来说,重点是本地DNS缓存。Android基于Linux系统,对于Android App来说,这个缓存又多了java层。

2.1 使用场景

当然,我们需要明白在Android 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;

  

三、Android DNS缓存更新

3.1、修正缓存过期时间

在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 Arrays.asList(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 Lists.newArrayList();
        }
    }

    /**
     * 清除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.3、DNS防篡改与安全

Android 4.3之前的DNS可能存在被污染的可能,如修改resolv.conf文件,在Android 4.3+之后,统一使用Netd方式,安全性上有所提高。因此,对Android 4.3之前的系统,建议使用HttpDNS等方案,此外采取HTTPS的通信方式,一定程度上几乎可以绝对避免此类问题的发生。

此外,我们在ip与域名对应数量不大的app中,可以在App中提前建立不同机房的域名映射也是一种放置篡改的方案。

3.4、Android底层DNS更新

Android基于linux,底层通过Libcore.so更新DNS,目前没有方式来更新Linux层面的DNS缓存。那么,我们的DNS-Prefetch功能是否有必要呢?这个问题我们需要明确,虽然我们不一定能更新底层DNS,但是,可以促进底层DNS更新,类似System.gc()的作用。

原文地址:https://www.cnblogs.com/1157760522ch/p/10906971.html

时间: 2024-10-12 09:30:27

Android DNS更新与DNS-Prefetch的相关文章

DNS&BIND——动态更新的DNS主从复制

本文配置的正向解析的主从服务,反向同理,不赘述了.... 从服务器应该是一台独立的名称服务器(首先要成为缓存服务器) 主动通知的必要条件(i或ii,满足其一即可) 主服务器的区域解析库文件中,必须有一条NS记录是指向从服务器(主动通知) master: vim  /etc/named.rfc1912.zones also-notify {slave_ip;}; 从服务器只需要定义区域.而无需提供解析库文件; 解析库文件自动同步至/var/named/slaves目录中 主服务器得允许从服务器作区

android网络交互之DNS优化知识整理

android网络交互之DNS优化知识整理 之前的工作中,经常会遇到DNS解析出问题导致网络交互的操作无法正常进行. 在很多的移动开发过程中,与服务端的交互的url通常是包含域名的.而在实际的网络交互的过程中,第一步就需要对域名进行dns解析. 复杂的网络环境里面,dns解析会耗费很长的时间.甚至是解析失败.这是经常会发生的. 所以这一步的优化是非常至关重要的. 有那么一种方案叫:IP直连 就是在网络交互的过程中,跳过域名的DNS解析,直接用IP进行网络交互.可以避免这一大麻烦. 针对这一方案,

解决Android SDK 更新难的问题

通常情况下呢,我们更新和下载Android SDK 都需要连接谷歌服务器,但是呢,由于国内水深火热的网络状况,下载速度基本为0或直接Time out 不过还好,国内有一个更新的镜像地址可以帮我们在不FQ的情况也能快速更新SDK(首先说明一下,本文是基于mac电脑的,windows电脑没有经过测试,不过问题应该不大) 首先我们需要配置一下hosts: Hosts是一个没有扩展名的系统文件,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址

CentOS6.6搭建DNS及主从DNS服务搭建

DNS搭建及主从DNS构建 DNS工作原理 DNS解析的作用 我们访问网络时,通常采用浏览器访问web站点,一般通过http://www.baidu.com或baidu.com等域名方式访问,也可以通过http://百度IP:端口进行访问,以前者常用,那么域名方式访问时怎么实现的呢?这就需要通过DNS服务器来解析了: DNS解析方式 正向解析: 根据域名查找其对应的IP地址:目前最常用的方式: 反向解析: 根据IP地址解析其对应的域名:一般应用于安全防护等领域: DNS的分布结构 DNS分布结构

DNS系列- 1.dns基本概念介绍

DNS系列- 1.dns基本概念介绍     目录         前言         一.概述             1.名词解释             2.DNS域名结构         二.DNS域名解析             1.查询类型             2.解析类型             3.DNS服务器的类型             4.区域传输             5.解析过程             6.解析答案         三.资源记录        

dns 后续(dns集群,“花生壳”,“远程IP密码更改dns”)

dns集群(多台服务器同步一个主dns信息,缓解了主dns的压力) 配置辅助dnf服务器(使它能同步主dns,分担主dns的压力:)修改配置文件 /etc/named.rfc1912.zonezone "dd.com" IN {   type slave;   masters {172.25.254.131 ;};  //同步谁的dns信息   file "slaves /dd.com.zone";  //将主dns的信息同步到 /var/named/slaves目

通过开源程序同时解决DNS劫持和DNS污染的问题

我们知道,某些网络运营商为了某些目的,对DNS进行了某些操作,导致使用ISP的正常上网设置无法通过域名取得正确的IP地址.常用的手段有:DNS劫持和DNS污染.关于DNS劫持和DNS污染的区别,请查找相关文章. 对付DNS劫持的方法很简单,只需要把系统的DNS设置改为为国外的DNS服务器的IP地址即可解决.但是对于DNS污染,一般除了使用代理服务器和 VPN之类的软件之外,并没有什么其它办法.但是利用我们对DNS污染的了解,还是可以做到不用代理服务器和VPN之类的软件就能解决DNS污染的问题,

DNS污染和DNS劫持的解决办法

DNS污染是指一些刻意制造或无意中制造出来的域名服务器分组,把域名指往不正确的IP地址. DNS劫持又称域名劫持,是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围以外的请求放行,否则返回假的IP地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能访问或访问的是假网址. DNS污染解决方法 1.使用各种SSH加密代理,在加密代理里进行远程DNS解析,或者使用VPN上网. 2.修改hosts文件,操作系统中Hosts文件的权限优先级高于DNS服务器,操作系统在访问某个域名

离线安装 Android Studio 更新

离线安装 Android Studio 更新 1.在线更新 随着 Android Studio 的越来越完善与流行,无论从功能性,还是性能上,它正在成为广大 Android 开发者的首选.但是因为总所周知墙的原因,我们在 Android Studio 内更新时,会无法更新: 这时,要么去官网下载最新的 Android Studio 安装包--但你如果连更新也无法检测的话,按说也访问不到 Android 开发者官网.悲剧 ^-^!!但我们还有另一种"曲线救国"的方法来更新,就是获取更新离