网络编程(7)-Android中使用HttpDns

主目录见:Android高级进阶知识(这是总目录索引)
新浪HttpDns框架:HTTPDNSLib
根据新浪HttpDns框架改造的库:HttpDNS

 今天这篇也算是比较实用的一篇文章了,想起以前使用webView的时候,偶尔的时候会出现Url加载出来的不是自己的页面,而是一个奇奇怪怪的页面,这也是典型的页面被劫持了,今天这篇文章会带我们来解析一下新浪的HttpDns框架HTTPDNSLib ,当然这里会以改造过的基于Android Studio的项目做讲解,如果大家对HttpDns不了解的话,这里推荐一篇文章Android端打开HttpDns的正确姿势,这篇文章还是有实践经验的,借助新浪一张图来镇楼:

HttpDns 交互流程

一.目标

今天的目标如下:
1.通过了解新浪HttpDns框架的思想来丰富自己的设计思路;
2.利用HttpDns来改进自己的接口服务。

二.源码分析

首先我们来看下Github中描述的这个框架的使用步骤:
1.配置清单文件 - AndroidManifest.xml

<!-- 权限信息 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

2.初始化调用 - 【建议放在Application中处理】

DNSCache.Init(context);

3.预加载域名解析(可选) - 【建议放在Application中处理】 可提前预加载需要解析的域名,并将解析数据缓存到内存中。

DNSCache.getInstance().preLoadDomains(new String[]{"api.camera.weibo.com","domain2","domain3"});

4.开始使用
直接调用该方法获取 A记录对象

DomainInfo[] infoList = DNSCache.getInstance().getDomainServerIp( "http://api.camera.weibo.com/index.html" ) ; 

//DomainInfo 返回有可能为null,如果为空则使用域名直接请求数据吧~ 因为在http server 故障的时候会出现这个问题。 
if( infoList != null ) { 
  //A记录可能会返回多个, 没有特殊需求直接使用第一个即可。  这个数组是经过排序的。 
  DomainInfo domainModel = infoList[0] ;  
  //这里是 android 请求网络。  只需要在http头里添加一个数据即可。 省下的请求数据和原先一样。
  HttpGet getMethod = new HttpGet( domainModel.url );  
  getMethod.setHeader("host", domainModel.host);
  HttpClient httpClient = new DefaultHttpClient();  
  HttpResponse response = httpClient.execute(getMethod); 
}

通过上面我们已经了解了大概的使用流程,那么我们从使用入手来分析。

1.初始化

我们首先来看初始化做了些什么,我们跟进DNSCache#Init(context)方法:

 public static void Init(Context ctx) {
        if (null == ctx){
            throw new RuntimeException("DNSCache Init; context can not be null!!!");
        }
        sContext = ctx.getApplicationContext();
        // 根据配置文件 初始化策略
        //DNS全局配置文件包括(配置文件地址(需要修改),HTTP_DNS 服务器API地址,自己家
        //HTTP_DNS服务API地址, 测速间隔时间,timer轮询器的间隔时间等等)
        DNSCacheConfig.InitCfg(sContext);
        //获取网络类型是wifi还是3g,4g,mac地址,运营商代码,运营商名称
        NetworkManager.CreateInstance(sContext);
       //获取缓存文件夹,应用版本名,appkey,设备id
        AppConfigUtil.init(sContext);
        //网络状态变化广播:网络变化则更新网络信息
        NetworkStateReceiver.register(sContext);
        Instance = null;
    }

上面已经注释的很清楚了,其实这个库里面的类是什么作用在[HttpDNS]是有说明的,我们先来看下全局配置文件具体初始化了什么:
全局配置初始化

    /**
     * 初始化 配置文件。 步骤:1.获取本地缓存文件,若没有则创建默认配置
     * 
     * @param ctx
     */
    public static void InitCfg(final Context ctx) {
        SharedPreferences sharedPreferences = ctx.getSharedPreferences("HttpDNSConstantsJson", Context.MODE_PRIVATE); // 私有数据
        Data data = null;
        try {
            
            String text = sharedPreferences.getString("ConfigText", "" );
            
            if (text == null || text.equals("")) {
                Tools.log("TAG_NET", "text = " + text);
                //这个方法主要是创建一些基本配置包括httpdns服务器地址,测速间隔时间,
                //timer轮询器的间隔时间等等
                data = Data.createDefault();
                //保存到本地sharedPreferences,然后进行跟各个类中需要的配置进行同步更新
                saveLocalConfigAndSync(ctx, data);
            }else{
                data = Data.fromJson(text);
                //如果有的话就直接同步更新
                syncConfig(data);
            }
            
            
        } catch (Exception e) {
            e.printStackTrace();
            // 上报错误 
            SharedPreferences.Editor editor = sharedPreferences.edit();
            editor.putString("ConfigText", "");
            boolean is = editor.commit();
            if (is)
                //上报错误初始化本地的默认配置
                InitCfg(ctx);
        }
        //拉取服务器的配置
        pullConfigFromServer(ctx);
    }

这里我们看到DNSCacheConfig初始化主要是初始化一些基础配置,这里的Data类里面需要配置上自己的一些地址,或者认为合理的配置。
网络初始化
接下来我们来看下网络管理类做了什么工作,初始化的时候具体如下:

/**
     * 初始化网络环境信息数据,可重复
     */
    public void Init() {

        new Thread(new Runnable() {
            public void run() {
                // TODO Auto-generated method stub
                
                Thread.currentThread().setName("Net Work Manager Init"); 
                //获取网络类型,-1为网络不可用;0为未知网络;1为WIFI;2为2G;3为3G;4为4G
                NETWORK_TYPE = Util.getNetworkType() ; 
                
                switch( NETWORK_TYPE ){
                
                case Constants.NETWORK_TYPE_UNCONNECTED:
                case Constants.NETWORK_TYPE_UNKNOWN:
                    break;
                    
                case Constants.NETWORK_TYPE_WIFI:
                    //获取本机ip
                    IP_ADDRESS = Util.getLocalIpAddress();
                    //获取本机Mac地址
                    MACADDRESS = Util.getRouteMac() ;
                    //获取wifi运营商
                    SP_TYPE = Util.getWifiSp();
                    break;
                    
                case Constants.NETWORK_TYPE_2G:
                case Constants.NETWORK_TYPE_3G:
                case Constants.NETWORK_TYPE_4G:
                    SP_TYPE = Util.getSP();
                    break;
                }
                //将网络类型转化为字符串比如:"WIFI网络","2G网络"等
                NETWORK_TYPE_STR = Constants.NETWORK_TYPE_TO_STR(NETWORK_TYPE) ;
                
                if( NETWORK_TYPE != NETWORK_TYPE_WIFI){
                 //将wifi运营商名字转化为字符串,比如:中国联通,中国移动等
                    SP_TYPE_STR = Constants.SP_TO_STR(SP_TYPE) ;
                }else{
                    SP_TYPE_STR = Util.getWifiSSID(NetworkManager.sContext) ;
                }
            }
        }).start();
    }

我们看到上面的代码还是非常简单的,就是获取一些网络相关的信息,我们这里就不详细说了,再则由于AppConfigUtil的初始化很简单,基本没啥代码,我们就直接说网络状态变化广播了:
网络状态广播注册

  public static void register(Context context) {
        IntentFilter mFilter = new IntentFilter();
        mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(new NetworkStateReceiver(), mFilter);
    }

我们看到这里就是注册了一个网络状态变化广播,如果我们接受到网络状态变化的广播,那么就会回调到广播接受者的onReceive()方法里面,我们来看看这里面做了些什么:

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();
        if (TextUtils.equals(action, ConnectivityManager.CONNECTIVITY_ACTION)) {
            // 获取活动网络连接信息
            NetworkInfo networkInfo = getActiveNetwork(context);
            if( networkInfo != null ){ 
                
                // 刷新网络环境
                if( NetworkManager.getInstance() != null ) {
                    //初始化初始化网络环境信息数据
                    NetworkManager.getInstance().Init(); 
                    if( DNSCache.getInstance() != null ){//会做清除缓存操作
                      DNSCache.getInstance().onNetworkStatusChanged(networkInfo); 
                    }
                }
            }
        }

我们看到这里会获取最新的活动网络信息然后重新初始化网络环境信息数据,同时会做清除内存缓存操作,主要是域名数据模型类中的数据。

2.预加载域名解析

我们知道,前面我们已经初始化了一些基本配置,这里主要是预先加载几个域名进行提前解析,然后进行缓存,我们直接看下这个方法:

 public void preLoadDomains(final String[] domains) {
        for (String domain : domains) {
            checkUpdates(domain, true);
        }
    }

这里主要是传进来一组想要预先加载的域名数组,然后遍历调用checkUpdates()方法进行操作:

 private void checkUpdates(String domain, boolean speedTest) {
        if (isSupport(domain)) {
            final String host = domain;
            final boolean needSpeedTest = speedTest;
            //从mRunningTasks(域名为key,线程为value)中根据域名获取对应的线程
            UpdateTask task = mRunningTasks.get(host);
            if (null == task) {//该域名没有对应的线程则创建
                UpdateTask updateTask = new UpdateTask(new Runnable() {
                    @Override
                    public void run() {
                        Thread.currentThread().setName("Get Http Dns Data");
                        //根据 host 更新数据
                        getHttpDnsData(host);
                        //移除对应的host的线程
                        mRunningTasks.remove(host);
                        if (needSpeedTest) {
                            //重新开一个线程进行测速操作
                            RealTimeThreadPool.getInstance().execute(new SpeedTestTask());
                        }
                    }
                });
                mRunningTasks.put(host, updateTask);
                updateTask.start();
            } else {
                long beginTime = task.getBeginTime();
                long now = System.currentTimeMillis();
                // 上次拉取超时,这次再开一个线程继续
                if (now - beginTime > 30 * 1000) {
                    task.start();
                }
            }
        }
    }

这个方法的逻辑也是非常简单的,就是开启一个线程利用这个域名更新数据,然后再开启一个线程进行测速,我们这里先来看看是怎么更新数据的,我们看到getHttpDnsData()方法:

 private final DomainModel getHttpDnsData(String host) {

        // 获取 httpdns 数据
        HttpDnsPack httpDnsPack = dnsManager.requestDns(host);

        if (httpDnsPack == null) {
            return null; // 没有从htppdns服务器获取正确的数据。必须中断下面流程
        }

        HttpDnsLogManager.getInstance().writeLog(HttpDnsLogManager.TYPE_INFO, HttpDnsLogManager.ACTION_INFO_DOMAIN, httpDnsPack.toJson(),
                true);
        // 插入本地 cache
        DomainModel domainModel = dnsCacheManager.insertDnsCache(httpDnsPack);

        return domainModel;
    }

我们看到这里的主要方法是DnsManager#requestDns(),主要的通过httpdns服务器获取数据就是这个方法,所以我们先来看看DnsManager类初始化的时候初始化了哪几个dns服务请求类,我们看下构造函数即可知道:

  public DnsManager() {
        mDnsProviders.add(new SinaHttpDns());
        mDnsProviders.add(new HttpPodDns());
        mDnsProviders.add(new UdpDns());
        mDnsProviders.add(new LocalDns());
    }

我们看到这里提供了四个dns服务请求类,第一个是新浪的,第二个是poddns,然后就是udp协议的dns,再者就是本地dns服务,我们这里先来看看新浪的httpdns请求类:

public class SinaHttpDns implements IDnsProvider{

    private INetworkRequests netWork;
    private JavaJSON_SINAHTTPDNS jsonObj;
    private String usingServerApi = "";
    public SinaHttpDns() {
        //okhttp网络请求类
        netWork = new OkHttpNetworkRequests();
        //解析新浪httpdns服务器返回的json数据
        jsonObj = new IJsonParser.JavaJSON_SINAHTTPDNS();
    }

    @Override
    public HttpDnsPack requestDns(String domain) {
        String jsonDataStr = null;
        HttpDnsPack dnsPack = null;
        ArrayList<String> serverApis = new ArrayList<String>();
      //将几个httpdns服务器的服务地址添加进来
        serverApis.addAll(DnsConfig.SINA_HTTPDNS_SERVER_API);
        while (null == dnsPack && serverApis.size() > 0) {
            try {
                String api = "";
              //这里usingServerApi初始化为空字符,所以第一次会取出第一个服务地址,
              //然后会赋值usingServerApi,
              //如果后面服务地址有重复前面的则会取出来先请求
                int index = serverApis.indexOf(usingServerApi);
                if (index != -1) {
                    api = serverApis.remove(index);
                } else {
                    api = serverApis.remove(0);
                }
                String sina_httpdns_api_url = api + domain;
                //请求httpDns服务器然后把返回的解析了封装在HttpDnsPack 类中
                jsonDataStr = netWork.requests(sina_httpdns_api_url);
                dnsPack = jsonObj.JsonStrToObj(jsonDataStr);
                usingServerApi = api;
            } catch (Exception e) {
                e.printStackTrace();
                usingServerApi = "";
            }
        }
        return dnsPack;
    }

    @Override
    public boolean isActivate() {
        return DnsConfig.enableSinaHttpDns;
    }
    
    //获取当前正在请求的httpDns服务器,如果没有就返回请求服务器地址集合的第一个
    @Override
    public String getServerApi() {
        String serverApi = "";
        if (!TextUtils.isEmpty(usingServerApi)) {
            serverApi = usingServerApi;
        } else {
            boolean yes = DnsConfig.SINA_HTTPDNS_SERVER_API.size() > 0;
            if (yes) {
                serverApi = DnsConfig.SINA_HTTPDNS_SERVER_API.get(0);
            }
        }
        return serverApi;
    }
    //这里的优先级主要是为了跟几个dns服务器进行优先级排序的
    @Override
    public int getPriority() {
        return 10;
    }
}

我们看到这个类的作用主要就是为了请求dns服务器,然后把请求结果解析进行缓存。几个dns服务器的做法都是大同小异,大家可以自行查看,我们接着看DnsManager#requestDns()方法:

  @Override
    public HttpDnsPack requestDns(String domain) {
        //对几个dns服务器请求类进行根据优先级排序
        Collections.sort(mDnsProviders, new Comparator<IDnsProvider>() {
            @Override
            public int compare(IDnsProvider lhs, IDnsProvider rhs) {
                if (lhs == null || rhs == null) {
                    return 0;
                } else {
                    // 按照降序排序
                    return rhs.getPriority() - lhs.getPriority();
                }
            }
        });
      //遍历
        for (IDnsProvider dp : mDnsProviders) {
            Tools.log("TAG", "访问" + dp.getClass().getSimpleName() + "接口开始," + "\n优先级是:" + dp.getPriority() + "\n该模块是否开启:" + dp.isActivate()
                    + "\n该模块的API地址是:" + dp.getServerApi());
            if (dp.isActivate()) {
                //把每个请求类取出调用他的requestDns请求服务器返回json进行解析
                HttpDnsPack dnsPack = dp.requestDns(domain);
                Tools.log("TAG", "访问" + dp.getClass().getSimpleName() + "接口结束," + "\n返回的结果是:" + dnsPack);
                if (null != dnsPack) {
                    if (DNSCacheConfig.DEBUG) {
                        if (null != debugInfo) {
                            debugInfo.add(dnsPack.rawResult + "[from:" + dp.getClass().getSimpleName() + "]");
                        }
                    }
                    //获取本地的运营商id
                    dnsPack.localhostSp = NetworkManager.getInstance().getSPID() ;
                    if( !dnsPack.device_sp.equals( dnsPack.localhostSp ) ){
                      //如果本地网络返回的运营商和服务器返回的运营商不同则写日志
                        HttpDnsLogManager.getInstance().writeLog(HttpDnsLogManager.TYPE_ERROR, HttpDnsLogManager.ACTION_ERR_SPINFO, dnsPack.toJson() );
                    }
                    
                    return dnsPack;
                }
            }
        }
//出错则写日志
     HttpDnsLogManager.getInstance().writeLog(HttpDnsLogManager.TYPE_ERROR, HttpDnsLogManager.ACTION_ERR_DOMAININFO, "{\"domain\":" + "\"" + domain + "\"}" );
        
        return null;
    }

这个方法就是用各个dns服务器请求类去请求json信息,json信息里有域名信息, 请求的设备ip, httpdns 接口返回的a记录等等信息。接下来我们看下测速模块,因为我们知道请求完dns服务器之后就会用这些信息主要是ip进行测速。

3.测速

测速模块主要是从DNSCache#SpeedTestTask中的run开始的,SpeedTestTask类实现了Runnable接口,因为这个类不是很复杂,这里就直接来看:

  class SpeedTestTask implements Runnable {

        public void run() {
            //获取内存缓存中所有的域模型数据
            ArrayList<DomainModel> list = dnsCacheManager.getAllMemoryCache();
            updateSpeedInfo(list);
        }

        private void updateSpeedInfo(ArrayList<DomainModel> list) {
            for (DomainModel domainModel : list) {
                //获取域模型中所有的ip模型集合
                ArrayList<IpModel> ipArray = domainModel.ipModelArr;
                if (ipArray == null || ipArray.size() < 1) {
                    continue;
                }
                for (IpModel ipModel : ipArray) {
                    //进行测速
                    int rtt = speedtestManager.speedTest(ipModel.ip, domainModel.domain);
                    boolean succ = rtt > SpeedtestManager.OCUR_ERROR;
                    if (succ) {
                        ipModel.rtt = String.valueOf(rtt);
                        ipModel.success_num = String.valueOf((Integer.valueOf(ipModel.success_num) + 1));
                        ipModel.finally_success_time = String.valueOf(System.currentTimeMillis());
                    } else {
                        ipModel.rtt = String.valueOf(SpeedtestManager.MAX_OVERTIME_RTT);
                        ipModel.err_num = String.valueOf((Integer.valueOf(ipModel.err_num) + 1));
                        ipModel.finally_fail_time = String.valueOf(System.currentTimeMillis());
                    }
                }
                //对 ip 进行排序
                scoreManager.serverIpScore(domainModel);
                //批量更新ip表数据
                dnsCacheManager.setSpeedInfo(ipArray);
            }
        }
    }

从上面程序我们看出测速主要是在SpeedtestManager类中进行的,同样地,我们看下这个类的构造函数:

 public SpeedtestManager() {
        mSpeedTests.add(new Socket80Test());
        mSpeedTests.add(new PingTest());
    }

我们看到这里的测速主要是用socket与ping两种方式进行测速,我们就来看下这两种方法的测速方法:

public class Socket80Test extends BaseSpeedTest {

    static final int TIMEOUT = 5 * 1000;

    @Override
    public int speedTest(String ip, String host) {
        Socket socket = null;
        try {
            long begin = System.currentTimeMillis();
            Socket s1 = new Socket();
            //socket连接该ip地址的80端口
            s1.connect(new InetSocketAddress(ip, 80), TIMEOUT);
            long end = System.currentTimeMillis();
            int rtt = (int) (end - begin);
            return rtt;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != socket) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return SpeedtestManager.OCUR_ERROR;
    }

    @Override
    public int getPriority() {
        return 10;
    }

    @Override
    public boolean isActivate() {
        return true;
    }
}

socket这种方式测速非常简单,就是通过socket连接对应ip的80端口,然后看下连接时间,接着我们看下ping的方式:

public class PingTest extends BaseSpeedTest{

    @Override
    public int speedTest(String ip, String host) {
        try {
            return Ping.runcmd("ping -c1 -s1 -w1 " + ip);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return SpeedtestManager.OCUR_ERROR;
    }

    public static class Ping {
        // ping -c1 -s1 -w1 www.baidu.com //-w 超时单位是s
        private static final String TAG_BYTES_FROM = "bytes from ";

        public static int runcmd(String cmd) throws Exception {
            Runtime runtime = Runtime.getRuntime();
            Process proc = null;

            final String command = cmd.trim();
            long startTime = System.currentTimeMillis();
            //调用doc指令执行ping操作
            proc = runtime.exec(command);
            proc.waitFor();
            long endTime = System.currentTimeMillis();
            InputStream inputStream = proc.getInputStream();
            String result = "unknown ip";

            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder resultBuilder = new StringBuilder();
            String line = "";
            while (null != (line = reader.readLine())) {
                resultBuilder.append(line);
            }
            reader.close();
            String responseStr = resultBuilder.toString();
            result = responseStr.toLowerCase().trim();
            if (isValidResult(result)) {
                return (int) (endTime - startTime);
            }
            return SpeedtestManager.OCUR_ERROR;
        }

        private static boolean isValidResult(String result) {
            if (!TextUtils.isEmpty(result)) {
                if (result.indexOf(TAG_BYTES_FROM) > 0) {
                    return true;
                }
            }
            return false;
        }
    }

    @Override
    public int getPriority() {
        return 0;
    }

    @Override
    public boolean isActivate() {
        return false;
    }
}

ping的方式也非常简单,其实就是执行doc控制台的ping指令,通过ping指令来查看这个ip访问需要的速度。测速工作完成之后我们会进行ip地址的排序。

4.ip地址排序

排序的工作主要是由ScoreManager类来负责的,我们来跟进它的serverIpScore()方法来看看:

    @Override
    public String[] serverIpScore(DomainModel domainModel) {

        String[] IpArr = null ;

        // 数据库中得数据,进行排序 , 当ipmodelSize 大于1个的时候在参与排序
        if (domainModel.ipModelArr.size() > 1) {
            if (IS_SORT) {
                plugInManager.run(domainModel.ipModelArr);
            } else {
                Tools.randomSort(domainModel.ipModelArr);
            }
        }
        
        // 转换数据格式
        IpArr = ListToArr(domainModel.ipModelArr) ; 

        return IpArr;
    }

我们看到当ipmodelSize大于1且开启了排序开关IS_SORT,才会使用plugInManager类进行排序,这里的随机排序我们就不说了,我们来看plugInManager类中是怎么排序的,同样地,我们首先来看他的构造函数:

  public PlugInManager() {

        plugIn.add(new SpeedTestPlugin()); // 速度插件
        plugIn.add(new PriorityPlugin()); // 优先级推荐插件
        plugIn.add(new SuccessNumPlugin());//成功次数插件
        plugIn.add(new ErrNumPlugin()); // 历史错误次数插件
        plugIn.add(new SuccessTimePlugin());//最后成功时间插件
    }

我们看到这个函数里面的各个插件都是用来给各个ip进行评分的,最终得分高的就会排在前面,当然这些插件你可以调整他们的权重,也许你更偏向于速度的评估,你可以调高速度插件的评分权重,这个需要在实际应用中去实践,查找到一个好的权重分配,这里初始的权重是这样的:

  public static float SpeedTestPluginNum = 40;
    public static float PriorityPluginNum = 60;
    public static float SuccessNumPluginNum = 10;
    public static float ErrNumPluginNum = 10;
    public static float SuccessTimePluginNum = 10;

我们首先来看速度这个插件:

public class SpeedTestPlugin implements IPlugIn {
    @Override
    public void run(ArrayList<IpModel> list) {
        // 查找到最大速度
        float MAX_SPEED = 0;
        for (IpModel temp : list) {
            if (temp.rtt == null || temp.rtt.equals(""))
                continue;
            float finallySpeed = Float.parseFloat(temp.rtt);
            MAX_SPEED = Math.max(MAX_SPEED, finallySpeed);
        }
        // 计算比值
        if (MAX_SPEED == 0) {
            return;
        }
        float bi = getWeight() / MAX_SPEED;
        // 计算得分
        for (IpModel temp : list) {
            if (temp.rtt == null || temp.rtt.equals("")){
                continue;
            }
            float finallySpeed = Float.parseFloat(temp.rtt);
            temp.grade += (getWeight() - (finallySpeed * bi));
        }
    }
    @Override
    public float getWeight() {
        return PlugInManager.SpeedTestPluginNum;
    }
    @Override
    public boolean isActivated() {
        return true;
    }
}

我们看到这个程序里面主要是根据各个ip的访问速度跟所有ip里面访问速度最快的进行比例然后乘以权重然后得到最终的得分。其他评价插件也是类似的,我们就不一一看了。

5.定时任务

我们看到DNSCache类构造函数中除了初始化一些类之外还会开启一个定时任务,具体如下:

 public DNSCache(Context ctx) {
        dnsCacheManager = new DnsCacheManager(ctx);
        queryManager = new QueryManager(dnsCacheManager);
        scoreManager = new ScoreManager();
        dnsManager = new DnsManager();
        speedtestManager = new SpeedtestManager();
        startTimer();
    }

我们看到这里除了初始化,确实是调用startTimer()方法开启了一个定时任务,具体我们跟进去就知道了:

  /**
     * 启动定时器
     */
    private void startTimer() {
        timer = new Timer();
        timer.schedule(task, 0, sleepTime);
    }

 private TimerTask task = new TimerTask() {

        @Override
        public void run() {
            TimerTaskOldRunTime = System.currentTimeMillis();
            //无网络情况下不执行任何后台任务操作
            if (NetworkManager.Util.getNetworkType() == Constants.NETWORK_TYPE_UNCONNECTED || NetworkManager.Util.getNetworkType() == Constants.MOBILE_UNKNOWN) {
                return;
            }
            /************************* 更新过期数据 ********************************/
            Thread.currentThread().setName("HTTP DNS TimerTask");
            final ArrayList<DomainModel> list = dnsCacheManager.getExpireDnsCache();
            for (DomainModel model : list) {
                checkUpdates(model.domain, false);
            }

            long now = System.currentTimeMillis();
            /************************* 测速逻辑 ********************************/
            if (now - lastSpeedTime > SpeedtestManager.time_interval - 3) {
                lastSpeedTime = now;
                RealTimeThreadPool.getInstance().execute(new SpeedTestTask());
            }

            /************************* 日志上报相关 ********************************/
            now = System.currentTimeMillis();
            if (HttpDnsLogManager.LOG_UPLOAD_SWITCH && now - lastLogTime > HttpDnsLogManager.time_interval) {
                lastLogTime = now;
                // 判断当前是wifi网络才能上传
                if (NetworkManager.Util.getNetworkType() == Constants.NETWORK_TYPE_WIFI) {
                    RealTimeThreadPool.getInstance().execute(new LogUpLoadTask());
                }
            }
        }
    };

上面的注释已经非常详细了,定时器主要就是做三个工作,更新过期的数据这里即是域数据,测速,然后日志上报。具体细节其实我们前面已经有说过了,这里就不赘述了。我们还知道最后使用的时候会调用DNSCache#getDomainServerIp(),其实代码逻辑也是非常简单,细节前面也都讲过,这里也不为了这个把文章弄得很长,大家可以自行查看哈。

总结:其实这个框架的实现逻辑还是蛮清晰的,并不是非常难,大家也可以认真了解原理,然后设计出自己的HttpDns框架,希望这个框架的分析能给大家一点小启发。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容