问:如何有效监控Android模拟器的HTTP访问情况?
答:前几个月,在调试某个应用时,需要监控应用与服务器之间的HTTP通讯。从搜索引擎找到的方案几乎全错。要么是人云亦云,要么是只能满足旧的平台版本,要么根本就是臆测。不得其解之际,用比较复杂的方法解决了。昨天想起来,觉得太窝囊,于是重整旗鼓,终于在官方文档上找到一点儿启发。把真正能解决问题的方案写了下来,一方面给同样遇到问题的朋友一点帮助,另一方面也是提醒自己,官方文档是解决问题的第一方案,但读文档也有讲究。不读文档、不思考,一遇问题就上网搜,结果大概都会事倍功半。
问题场景:在调试某个应用时,需要监控应用与服务器之间的HTTP通讯。
解决思路:第一时间想到Fiddler2。Android官方文档提到用TCPDump获得通讯封包或实时监控,好是好,有点儿高射炮打蚊子的意思。Fiddler2这个工具,界面友好,功能足够使用,可以说是Windows上最好的HTTP监控工具。Fiddler2用起来很方便,运行之后,即在本机建立一个代理(缺省设置为127.0.0.1:8888),其他访问HTTP的应用程序,把代理设为同一地址和端口即可。
实施过程:按照文档的指导,在启动模拟器时,使用-http-proxy参数,设置127.0.0.1:8888为模拟器的上网代理,结果失败。Fiddler2没有抓到通讯应答。上网搜索,许多文章或帖子(包括英文的和中文的)都说要修改模拟器的系统设置数据库,添加一条HTTP_PROXY的记录。尝试后也失败。
解决方案:再细读文档中有关模拟器的部分,看到Network Address Space一段,忽有所悟。这段文字是这么写的——
Each instance of the emulator runs behind a virtual router/firewall service that isolates it from your development machine’s network interfaces and settings and from the internet. An emulated device can not see your development machine or other emulator instances on the network. Instead, it sees only that it is connected through Ethernet to a router/firewall.
[译文] 模拟器在虚拟路由器、防火墙服务后面运行,这套路由器、防火墙服务隔离于开发机的网络界面、设置,也与互联网相隔离。模拟设备不能访问开发机或网络上的其他模拟器。它看到的只是自己通过以太网连接到一个路由器、防火墙。
这下子清楚了,模拟器压根不知道127.0.0.1是个啥地址,也压根访问不到。在127.0.0.1上设置的代理,自然也抓不到任何通讯应答了。文档接着写道,这个虚拟防火墙、路由器管理从10.0.2.2至10.0.2.24,模拟器的IP地址是10.0.2.15,而模拟器看到的开发机地址则是10.0.2.2。也就是说,从模拟器的角度看,Fiddler2是在10.0.2.2上运行的。于是用emulator命令加上avd -http-proxy 10.0.2.2:8888参数启动模拟器,在模拟器上运行browser,访问任何web地址,可以看到,Fiddler2抓到了HTTP通讯。在自己的应用程序中打开创建HttpURLConnection,访问同一网址(下面只是创建连接的代码,访问代码略):
private HttpURLConnection createConnection(URL url)
{
HttpURLConnection conn=null;
try
{
conn=(HttpURLConnection)url.openConnection();
}
catch (IOException e)
{
e.printStackTrace();
}
return conn;
}
这次Fiddler2没能抓到通讯。但应用程序访问网页是成功的。只在启动模拟器时加上-http-proxy参数还不够,应用程序创建连接时,也要指定HTTP代理才行:
private HttpURLConnection createConnectionWithProxy(URL url, String proxyAddress, int proxyPort)
{
HttpURLConnection conn = null;
Proxy proxy=new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress,proxyPort));
try {
conn = (HttpURLConnection) url.openConnection(proxy);
} catch (IOException e) {
e.printStackTrace();
}
return conn;
}
调用上述方法,传入代理地址10.0.2.2和端口8888,再次运行应用程序,这次可以了(截图和上图大同小异,略)。
这个问题其实很简单,关键还是——
1)对Android的熟悉程度;
2)好好阅读文档。
但是,什么也代替不了思考。其他问题的解决也是同理。