通常情况下,做移动开发时,如果要向后台请求数据,都会直接使用TCP通信。但实际上一来HTTP比TCP简单易用多了,二来有很多现有CGI如果要进行改造得花很大功夫。还是会有使用HTTP请求来拉取数据。
在做Android QQ二维码时,扫描到二维码字符串,就是把该字符串用HTTP传给后台,后台解析后返回给客户端,客户端再进行下一步处理。在提交测试后,测试同事发现,在移动网络上,DNS解析会经常失败,导致二维码扫描功能不可用。功能测试不通过,导致无法发布。同时测试喜欢在清空DNS缓存和屏蔽了DNS解析的情况下,二维码解析功能仍然可用。因此这里增加了如下处理:
- 进入“扫一扫”的界面时,客户端就开始对CGI所在域名进行解析,并把解析结果缓存到本地文件;
- 发起CGI请求时,还是使用原域名进行请求,如果DNS解析失败,则用第一步缓存的IP替换掉CGI中的域名再发起一次请求。
由于国内有好几个网络运营商,公司在不同的网络环境都有不一样的出口IP,因此选择最近的IP才能快速访问CGI。所以缓存域名结果时,需要区分网络分别保存。
more
这样,当用户的手机上至少成功使用了一次二维码扫描时,CGI对应的IP就保存下来了(至少有一个出口IP)。下次访问(同一次登录或者关闭手机QQ后再打开)时,即使DNS解析失败,还是能使用上一次保存的IP进行访问,可能会相应比较慢(用户从A网络切换到了B网络),但起码保证了功能的可用性。实现起来也不难,Java都有现成的接口了。
主动发起DNS解析只要调用InetAddress的接口即可,把获取到的结果保存到SharedPreferences供下次使用:
public static void lookupIP(Context context, String host){
SharedPreferences preferences = context.getSharedPreferences("host", 0);
String ip;
try {
InetAddress address = InetAddress.getByName(host);
ip = address.getHostAddress();
SharedPreferences.Editor editor = preferences.edit();
editor.putString(host, ip);
editor.commit();
QLog.d(TAG, "lookup address: " + host + ", ip: " + ip);
} catch (UnknownHostException e) {
}
}
然后在HTTP请求出现DNS解析错误的时候,用IP替换掉URL中的域名即可:
HttpResponse response = null;
try{
response = openRequest(context, url, host, method, params, header);
}catch (IOException e){
//DNS错误改用ip重新发一次
String ip = getIP(context, host);
if(ip != null){
url = url.replace(host, ip);
}
response = openRequest(context, url, host, method, params, header);
}
这里之所以不直接用上次保存的IP来替换域名进行访问,是因为在正常情况下,进入“扫一扫”的界面时,已经预先对所用域名进行了预解析,如果解析成功,调用openRequest时就不用DNS查询了;而如果预解析失败了,openRequest的时候还能再尝试一次,失败时才使用IP访问。同时,很多用户都会在移动网络和WIFI网络中切换,如果直接用IP替换域名,会出现用户当前是移动网络,而是用的域名IP是在教育网IP(上一次使用的是教育网WIFI)的情况,导致CGI响应变得更慢。
当然,更好的解决方法是:先检测当前用户的网络环境,如果本地保存了对应网络的IP,直接使用IP请求;如果没有,再改用域名来请求。
还有更进阶的方案:第一次登陆时,从服务器拉取一份host列表(每个域名包括各个网络环境的出口IP),把所有HTTP请求的域名都替换成相应IP,这样就能完美解决DNS不可用、DNS解析失败的情况了。之后维护host文件的更新即可。