网络性能

智能手机最重大的改革就是能将人类所有的知识存储到这个可以装进口袋的设备中。它可以帮助我们解答一些我们可能会被问到的重要的问题。(“爸爸,长颈鹿的声音是什么样的呢”),我们也可以用它与世界各地的完全陌生的人下棋或者玩其它类型的游戏。

随着网络吞吐量需求的增长,我们听说了各种有关更快更可靠的网络是如何将信息存放地离我们更近的言论。在这里我将打破你的幻想泡沫。尽管更新更快的网络已经来临,但是距离4G网络的广泛使用还需要几十年的时间。在此期间,我们可以重点关注以下三点:第一,应用程序使用现有网络的方式;第二,网络的使用对于程序运行的重要性;第三,网络对于设备电池的影响。正如我们在第三章中的结论:蜂窝无线网络、Wi-Fi、蓝牙这类方便信息交流的无线电也是导致电量耗损的主要因素。通过最大的优化设备中的网络性能,你的应用程序将运行的更快,同时消耗的电量更少。

在这一章中,我们将讨论以下三个问题:移动设备上使用不同无线信号之间的差异、介绍一些用来分析应用程序中网络使用情况的工具以及一些简单的可以取得巨大进步的修改技巧。我们将看一下如何在不同网络环境中测试你的应用程序(因为现在世界上大部分覆盖的还是2G和3G的网络,你必须确保你的应用程序在这些环境中是可以运行),最后我们看一下你设备上的一些其他无线电:蓝牙与手表等外设间的通信和GPS定位扫描。首先我们来看一下这些无线电是如何工作的,然后列出一些可以最大化其性能的使用方式。

无线网络与蜂窝无线电

无线网络对比蜂窝无线电?是不是与互联网的连接仅仅是与互联网的连接呢?事实上,这两种无线电的连接方式有很大的不同,根据你的应用程序需要多少数据,你可能想建构两种不同的模型来进行内容下载:一种是蜂窝无线电,另一种就是无线网络。

当连接互联网的时候,有两个连接的属性会导致性能限制:宽带(管道的大小)和延迟(管道的长度或者管道的拥挤程度)。我们接下来一起看下不同的无线连接是如何影响这两个属性的,以及在Android能耗统计文件(第30页中讲过”Android能耗统计文件”)中的功耗值差不多的情况下,当蜂窝比Wi-Fi活跃时,蜂窝无线电是如何消耗更多电量的。

Wi-Fi

Wi-Fi连接(在理想条件下)吞吐量比较大,是一种低延迟的连接,而且通常是不计费的(意味着使用Wi-Fi网络没有附加成本)。这里我之所以在描述时加上(理想条件)是因为,你很少处在理想的无线网络环境中。因为Wi-Fi 网络使用相同的频率,所以拥有多重Wi-Fi网络的区域将会交叠着有限的频率,导致所有网络中宽带的共享。

让我们假设你有一个没有宽带问题的Wi-Fi连接并且可以很好的连接到你的Android手机上。当你的应用程序试图建立一个Wi-Fi网络连接的时候会有一段非常小的延迟。当这种连接已经建立的时候,无线电将处于高功率状态。一旦数据发生转换,无线将会停止使用。在打开和关闭无线网络时将会有一点延迟(测试结果为:开启的延迟时间为80毫秒,关闭时的延迟为240毫秒)。正如我们在第三章第30页“Android能耗统计文件”中看到的那样,当Nexus6连接上Wi-Fi,且该手机处于待机模式时,消耗的电量为3mA,而当其处于活动状态有数据传送时,所使用的电量为240mA。考虑到Android设备电量的有限性,你会明白为什么快速高效的下载是至关重要的。

Wi-Fi具有高吞吐量、低延迟、没有数据收费的特点,这样你的应用程序在使用Wi-Fi的时候就可以以一种更加“渴望数据”的方式运行。你可以使用更高质量的图片和视频,或许你还可以与你的用户拥有更多的互动体验。

蜂窝

目前,有多种不同的蜂窝技术在世界各地使用。根据客户连接的网络类型不同,其体验可能完全不一样。正如第二章讨论的那样,世界各地还有很多人仍然在使用2G和3G网络。

See Table 7-1. 

表7-1. 无线网络的演变

当Android设备连接到蜂窝数据网络的时候,为了维持两者的连接,所消耗的电量将因网络信号的强弱而变化。在“Android能耗统计文件”(30页)那一节,你可能注意到我们可以使用两种模式来开启信号连接。当你处于强信号区域的时候,只需使用低数值模式就可以进行连接,但是如果你处于蜂窝数据连接的低覆盖区时,你必须开启天线功率才能维持连接。当信号建立连接后,设备的电流将从4.5mA跳到了125mA。虽然从原始数据上看,活跃的蜂窝无线信号(处于125mA)比Wi-Fi信号(处于240mA)耗电量低,但是考虑到蜂窝连接在网络中的实现方式,蜂窝连接的功耗通常会更高一些。为了最大限度的提高蜂窝网络的服务质量,所有运营商都采用了一种无线资源控制(RRC)的状态机来控制数据连接的建立与中断。

状态机

状态机(有时是一个有限状态机)描述了一些拥有有限状态事件的一种逻辑顺序。一种简单的状态机是具有开和关状态的灯具开关。而对于蜂窝网络而言,其网络连接的状态更多,并将其用于优化一些如网络、设备吞吐量、等待时间和电源功耗等因素。

无线资源控制(RRC)状态机

当您的手机启动数据连接后,在创建TCP连接之前, 会有几种初始无线信号发送到信号塔上。这些信号会使创建无线连接的时间增加500~1000ms。延迟是移动连接的关键方面之一,而状态机正是试图去抵消这种延迟。每一种移动网络都有一个RRC状态机,它用于保证在最后一个数据包发出之后无线电信号仍然开启,以补偿建立连接时产生的延迟并用于平衡电量消耗。

每个载波可以指定各种不同的状态以及设备在该状态停留的时间(正因如此,每个网络都稍有不同)。因为世界各地的每种网络具有不同的特定变量,因此掌握精确的时间并不是很重要,但是好好掌握RRC状态机的基本原理对于理解蜂窝连接的操作机理仍大有裨益。知道了这一点,你就能够优化你的网络连接,使之与RRC状态机协同工作,这样就能帮助你的应用程序运行更快,并且使用的电量更少。

状态机注意事项

去讨论(或列出)所有移动运营商使用的计时器(哪怕是同一个运营商,计时器也会因地区而异,并且会随着时间而改变),这远远超出了本书的范畴。作为开发者,去优化你的App同每一个运营商的RRC状态机的连接是不切实际的。重要的是要理解状态机是什么,以及状态机的存在对移动App有什么危害(或者是帮助)。

此外对于3G (GSM, CDMA) 网络和 LTE 网络,所使用的状态机是不同的。简单起见,这里我将介绍一下LTE RRC 状态机。  

4G (LTE) 状态机

当数据将要发送时,你的安卓手机将从空闲状态(低功率消耗)转变到连接状态(高功率消耗)。当数据包停止发送后,无线连接并没有立即关闭,而是有一个10~15s的高功率延续时间段。因为一旦无线连接立即关闭,那么如果接下来仍有后续数据快速的发送过来,这些数据将不得不再次跨越高延迟的连接时间段,这样的数据发送接收方式会使用户体验变得难以容忍。正因为无线连接仍然处于连接状态,后续发送的数据包延迟时间将大大减少,数据包将会被快速的运输。当最后的高功率延续时间段没有数据包时,无线连接将会关闭以节省电量。

图7-1. LTE RRC 状态机
如果你再看一下图表7-1,你会发现随着网络的不断改善,延迟在逐渐降低,同时网络的吞吐量也在逐渐增加。4G网络规范已经大大改善了建立数据连接(RRC从空闲状态向连接状态的过度)所需的信号,相比于3G网络,4G网络连接建立时的延迟时间已经下降了5-10倍(如图7-1所示)。曾经在3G中是300-1000MS,现在在LTE中是50-100ms。尽管宽带中4G的改善是令人印象深刻的,但真正使LTE像它展示的那样快的原因是由于延迟的改善。伊利亚.格林哥瑞克(Ilya Grigorik在他的作品《高性能网络浏览器》(O’Reilly出版)中使用大量篇幅描写了延迟的影响力。)另外他还更加细致的介绍了所有的网络性能。

一般情况下,LTE无线连接比3G无线连接耗费的电量更多。如果正在传输的文件很大,LET的下载速度可能要更快,结束无线连接的时间也会相应更短,因此使用的电量更少。但是,绝大多数的移动数据传输并不是大型文件,而是由数百个小型文件构成,因此使用的数据块也相对较小。这些小小的文件无法充分利用LTE的带宽能力(因为它们太小了),因此通过LTE下载内容所耗费的电量比3G要稍多一些。

无线连接和数据连接

无线连接和数据连接之间存在细微的差别,我们在此讨论一下。信号塔和手机之间的物理无线连接不同于手机和服务器之间的数据连接。数据连接是基于无线连接之上,也就是说,传输数据前必须先建立无线连接。打个比方,无线连接就像一座升降桥,而数据连接就是桥上的马路。

如果数据连接处于连接状态,但是目前并没有在传输数据,只是将来需要,那么手机和信号塔之间的无线连接可以暂时挂起(省电)。回到我们刚才的升降桥比喻,也就是说,马路还在那里,但是升降桥暂时处于吊起状态,让出通道让底下的船通过。如果服务器向设备发送数据,信号塔就会向设备发送无线信号,以重新建立无线连接,并允许数据连接的完成(放下吊桥,允许汽车在马路上穿行)。

这听起来很棒,我们会想,为什么不干脆让所有的连接都保持开启状态以备将来使用?由于蜂窝网络的连接数量有限,一段时间后(通常是5~30分钟),网络会自动清理孤立的连接(估计网络开发人员是想拆掉闲置的升降桥,改建河畔公寓)。

你的应用在使用RRC状态机?

如果你在使用RRC状态机,你的网络连接所消耗的电量绝对超出了你的想象。通过分组连接,并确保无线电使用时间最小化,可以极大地提高移动应用的性能。所有的数据连接传输数据至少要10秒(相当于5分钟的待机时间)。一直以来,很多人都认为数据下载速度是移动应用的性能关键。但是很显然,移动应用除了下载速度要快以外,尽可能少地开关无线电通信也是节省资源的关键。

手机通话时使用数据

我们生活在一个多任务化的世界。使用手机应用的过程中打电话是很常见的现象。如果你的应用是通过LTE连接,手机来电时会降到3G数据连接,以完成剩余的数据会话。 这是由电路交换回落导致的。

在 Voice over LTE(VoLTE)成为标准规范前,所有的语音通话传输都是通过3G电路交换网络。

这就意味着,正在活动的任何数据会话都会降到3G网络。本书第62页的“Battery Historian 2.0”中,我们讨论过一个研究——我在通话的同时进行电话会议。

仔细观察通话时间(见图7-2),我们发现无线连接一开始是蓝色(LTE),但是在通话过程中变成了黑色(HSPA)。通话结束时,无线电又转回了LTE数据网络(蓝色)。

图 7-2 Battery Historian 显示电路交换回落

测试工具

到目前为止,我们已经讨论了安卓的无线耗电问题,以及移动数据网络的运作方式。那么我们该如何使用这些知识来优化安卓应用的流量?再者,即使优化了流量,又如何进行测试和确认?我们有大量的工具可以捕获和分析移动流量数据。多年来,世界各地的网络运营人员都在使用Wireshark和Fiddler之类的工具收集封包数据并分析其中的潜在问题和优化方案。中间人(MITM)工具帮助用户解密HTTPS流量让用户了解哪些数据可以在网络上安全发送。这些毫无疑问都是测试移动应用性能的一线工具。AT&T开发了一款类似工具叫应用程序资源优化工具(ARO),它也可以捕获数据包,另外还为应用程序开发人员提供了移动通信的流量优化建议。

Wireshark

Wireshark可能是世界上应用最普遍的网络分析工具。它是一款免费的桌面运行工具,用于收集在数据连接上传送的网络封包,可以实时监测数据,并将数据收集起来存储于文件夹中用于进一步分析。

要使用Wireshark测试你的安卓手机,必须将手机连接到已安装Wireshark的PC上。如果是Windows机器,我使用Connectify将笔记本电脑转换成Wi-Fi热点,然后把安卓设备连接到热点,所有的数据流量开始从手机传输到电脑(可以被Wireshark监测到)。到这一步,你可以开始在Wireshark应用中通过无线接口收集数据(如果你不确定哪个是无线接口,启动手机的网络流量,你会看到有一个网络接口开始发送和接收数据包流量)。

图7-3中我们可以看到,Wireshark显示了我的手机(192.168.223.104)和电脑(192.168.223.1)之间来回发送的每一个数据包。一开始要弄明白其中的事件有点困难,所以我添加了一个HTTP过滤器,这样就能把数据包限制在HTTP数据包的范围内,然后我再观察测试过程中产生的请求和响应。现在你看到我在浏览器里打开cnn.com发起请求。数据包1481显示我的cnn.com请求发生301重定向(在红色方框中)至www.cnn.com,该页面向CDN催生了大量文件请求。如果我想知道这次抓包中产生了多少个301重定向,可以使用“http.response.code == 301”过滤查出三个这样的重定向。

图7-3. 使用Wireshark抓包

Wireshark里的过滤工具非常强大,可以搜索和过滤特定文件、特定类型的文件、带有缓存头的文件,等等(可能性数不胜数)。在实践中这些搜索都很强大,但是你必须要有针对性的问题,否则这种方法用起来就像大海捞针。相反地,如果你对问题有了大概的了解,Wireshark可以很好地帮助你深入挖掘并准确定位。

Fiddler

Fiddler是另一款分析网络流量的免费工具,为设备的所有传输数据充当代理。作为代理,Fiddler还可以充当中间人(MITM)工具,允许用户解密HTTPS流量。在Wireshark中你可以看到正在传输的HTTPS流量,但是你无法通过解密来获取文件类型或文件内容。

Fiddler的使用方式和Wireshark类似:把安卓设备连接到Connectify Wi-Fi热点,然后更改设备上的Wi-Fi设置添加Fiddler代理,在设备和PC上安装Fiddler证书,最后Fiddler就能够读取所有的传输数据(Fiddler的网站上有完整的安装说明)。

完成所有连接后,你可以在Fiddler应用窗口里看到流量传输信息。图7-4显示的屏幕截图是使用Fiddler代理抓取我在使用YellowPages应用查找周边的杂货店时传输的数据包的信息。

图7-4 使用Fiddler代理抓包

我们看图7-4中Fiddler packet capture(Fiddler抓包)的左侧窗口,方框区域的字段是一条来自YP应用程序的响应,文件是668个字节(连接中),缓存设置为“no-cache”,并且是一个JSON文件。它包含了我家到杂货店的所有路线,而且使用了HTTPS加密(非常好,因为响应文件里有我的地址和位置信息)。Fiddler窗口的右侧(见图7-5),有很多选项窗口。顶部窗口显示发送到syndication.yellowpages.com的请求标题,底部窗口显示被解密的响应。在JSON文件里,你会发现到杂货店的总距离是4.787665英里(真是相当地精确!)应用还进一步预测了开车到杂货店的时间是661秒,或者说是11分钟多一点。

图7-5 Fiddler代理抓包的窗口详情

MITMProxy

MITMproxy是一款类似于Fiddler的代理工具,通过创建一个MITM,允许解密当前网络上传输的HTTPS数据。解密HTTPS流量是非常有用的工具,因为很多数据流量都是使用HTTPS来保护客户的数据。Fiddler和MITMproxy也可以通过解密数据,确保你向任何已添加到应用程序的第三方SDK发送正确的文件和信息。

AT&tT应用程序资源优化工具

ARO是专为Android和iOS应用程序设计的一款网络性能监测工具,由AT&T开发,免费开源。它包含很多与Wireshark以及Fiddler相同的数据包捕获功能。但是又与Wireshark和Fiddler不同(二者需要通过Wi-Fi连接到计算机),ARO能够收集蜂窝网络的数据包跟踪信息。一旦ARO从测试中收集到数据,它会对数据进行处理,并为开发者提供友好的图表信息,帮助更好地分析数据。而且ARO会按照移动网络的25个最佳实例测试流量,并立即向测试者反馈哪些领域的性能可以改进。图7-6是测试总结(红色叉号表示失败,绿色勾号表示跟踪通过测试标准)。在本章中我们将讨论这些最佳实例。

图7-6 ARO最佳实例:通过/失败

适用于安卓设备的ARO有两个版本。ARO 数据收集器 APK直接在你的设备上运行TCPdump收集所有数据包(并为每个连接分配进程)。这依赖于安卓设备具备root权限,为了简化测试,也可以使用不需要root权限的版本。在没有取得root权限的情况下,我们无法将连接分配至特定进程,要将流量锁定至特定应用会有些困难(如果设备上运行很多应用)。

一旦你在ARO中跟踪采集到一个数据,就可以在ARO分析工具里分析。你在自己的应用里执行的测试会根据图7-6所示的25个最佳实践评测。每个最佳实践后面列出了更多的详细信息,如果没有通过任意一项最佳实践,你可以知道得知失败的缘由(图7-7)。

图7-7:ARO关于重复内容的最佳实践;多次发送的数据34%(9MB)太多了!(为方便阅读放大了一些文字)

每项数据跟踪都提供了5个选项卡,但是只有在诊断(Diagnostics)选项卡下你能真正看到数据是怎样流入和流出你的应用(图7-8)。

图7-8:ARO 诊断选项卡

诊断选项卡包含很多信息,让我们来看看这里给出的数据。显示的有两个窗口:左边是数据分析,右边是跟踪过程中的屏幕录像。录像与跟踪同步,所以当你选择一个数据包或连接时,可以看到那个时刻屏幕上显示的内容。

仔细看图7-9中的诊断选项卡,图中记录了随时间变化的数据包流量。最上行显示的是随时间变化的正常吞吐量。通过对比可以看出哪些连接的流量较大,哪些连接的流量较小。下面两行显示的是连接过程中上传和下载的数据包。标有“Bursts”的那一行用不同颜色表明了突发流量类型。红色的是由应用程序发起,黄色的是由服务器发起,绿色的发生在用户输入事件后(在下一行记录),蓝色突发流量通常是由于连接关闭产生空数据包。最下面一行表示LTE RRC状态机,如图7-1所示。纯色表示连续接收,网纹区域代表Tail。空白区域指无线网络关闭的时间(Idle)。请注意蓝色箭头和蓝色虚线。它代表的是录像查看器上显示的跟踪时刻。如果你点一下录像的播放按钮,会看到这条线向右移动,让你看到正在传输的数据包,同时还可以看到设备屏幕上的显示内容。

图7-9:ARO 诊断选项卡数据图

图下方的两个表对网络跟踪过程中发起的每项数据连接提供了更多信息(图7-10)。上面的表显示了跟踪过程中启动的每一项TCP/UDP连接。当前选中的是SiriusXM Radio在551秒的时候启动的连接。你可以看到域名和 IP 信息,以及字节计数和连接产生的数据包数。

下表显示了上表中选中的TCP连接发起的请求和响应,该表告诉我们此连接中传输了很多81KB的音乐文件。

图7-10 ARO诊断卡数据表

ARO中还有一些视图也提供了可观的信息量,我们在本章讨论潜在优化方案的时候可以看到更多的屏幕截屏。

ARO有个缺陷就是它无法解析通过HPPTS发送的任何文件详情。如果你的应用是使用HTTPS,你必须在Fiddler跟踪中手动查看这些文件。

混合型应用和WebPageTest.org

WebPageTest.org是一款强大的网站测试工具。我知道你在想,“这本书是关于安卓应用开发的,你怎么在讲网站测试的内容?”成千上万的安卓应用都是使用PhoneGap之类的工具开发出来的,这些工具只是简单地把来自网络的组件用原生代码包装起来,提供一种更加原生的应用体验。这种原生应用通常只在一个嵌入式的web视图中显示内容,让它表面上看起来原生。因为不能优化wrapper,作为一个混合型应用程序开发人员,你唯一能做的就是确保你的web组件运行够快。

WebPageTest允许你在全球几个不同的地方测试你的网站,但是在弗吉尼亚州的杜勒斯只有几台Motorola 和Nexus设备可以测试(带有Chrome和Chrome Beta)。WebPageTest测试可以显示你的网页在手机上的加载速度并指出哪些方面需要改进。

安卓网络优化

Web性能社区有许多关于网站的最佳实践,这些最佳实践同样适用于移动应用。我们将讨论几个安卓移动应用的最佳实践(它们也适用于你可能会做的所有iOS开发)。这里列举的优化最佳实践重要性不分先后,因为每一项都会对应用性能产生不同的影响(有些可能根本不适用于你的应用)。网络性能优化的总体趋势(无论是台式机还是移动终端)是以最快的速度下载任何东西。关闭无线通信,你可以节省电量。尽可能的以最快的速度将内容发送给用户,用户体验将会更好。

移动应用性能优化的基本原则基本上源自于Steve Sounders在 《高性能网站建设指南》中所列的14条标志性性能优化原则:

  • 减少HTTPS请求

  • 使用内容发布网络

  • 添加Expires头

  • 压缩组件

  • 将样式表放在顶部

  • 将脚本放在底部

  • 避免CSS表达式

  • 使用外部JavaScript和CSS

  • 减少DNS查找

  • 精简JavaScript

  • 避免重定向

  • 移除重复脚本

  • 配置Etag

  • 使Ajax可缓存

这里面有些原则是针对网站的,但是大部分还是适用于安卓本机优化,在以下几节内容中我们会讲到。

文件优化

有两种基本方法可以更快地下载数据:减少请求次数(Sounders的第一条原则),或者是减少这些请求的大小(Sounders的14条原则里有若干条是这么建议的)。这样做很让人为难,因为我们的应用变得日益复杂。但庆幸的是,本章中的建议可以帮助你找到一些方案来减少应用内容的数量以及大小。

压缩文本文件(压缩组件)

这是最简单的解决方式之一。我们向应用程序发送文本文件(HTML,CSS,JavaScript,JSON等)的时候,先在服务器上进行压缩,可以将文件大小减少4~8x。大幅度压缩服务器向应用程序发送的文件大小,意味着文件的传输次数更少而且发送速度更快。例如,我们在应用里下载了一个200KB的没有进行过GZip压缩的文本文件,把它放到已启用压缩的服务器上可以将在线大小减少到51KB。这样我们不仅可以把文件内容更快地发送给客户,而且还可以节约服务器带宽并减少损耗!

如今可用的Gzip算法有很多。一般情况下,大多数的应用程序采取标准的Gzip压缩就足够了。如果你真的想要最大限度的压缩,而且你的文件又不需要经常更改,你可以试试Zopfli压缩算法。跟默认的Gzip算法相比,它的压缩率要多出5%。但是有个缺点,它的文件压缩执行时间要多出100x(因此敬告大家“只用它处理预压缩过的文件”)。

启用Gzip压缩只需要对服务器做一个简单的更改(不需要更改应用代码),你只需要给.htaccess文件添加文件扩展名/MIME类型。

<ifModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</ifModule>

你立即就能看到文本文件的下载速度变快了。另外还可以给Gzip增设排除小文件的功能,把不足850字节的文件放到一个数据包里发送可以不用压缩。当然在Gzip压缩/解压缩负荷很小的情况下,你也可以省略这些过滤小文件的步骤。

ARO对跟踪过程中捕获的所有的文本文件进行Gzip压缩测试。你可以在ARO里的两处地方发现文件是否被压缩。

最佳实践:文本文件压缩

所有未经压缩便被发送的文本文件都列在Text File Compression(文本文件压缩)中(见图7-11)。目前,还没有办法可以直接获取存储文件来添加压缩,但是该表如实报告了未压缩的文件大小。请注意,不足850字节的文件都没有标记出来(因为它们在不压缩的情况下就能装入一个数据包一次传完)。

图7-11 ARO文本压缩最佳实践

诊断选项卡(请求响应表)

图7-8中,最下面的表格表示请求和响应。最右边的那一列是识别文本文件是否压缩,如果没有压缩显示的是“none”。

精简文本文件(Souders:精简JavaScript)

精简是另外一个缩小文本文件的方式,精简过程就是去掉文本文件中所有只具有方便阅读作用的格式(比如空格、制表符和注释)使文件变小。 例如:

<html>
 <title> A Sample Page</title>
<body>
with some sample text
<--do more here-->
</body>
</html>

修改为:

<html><title> A Sample Page</title><body>with some sample text</body></html>

根据页面大小和复杂度,通过精简你的文件可以缩小20%至50%。很多构建工具(如grunt)自带精简库,在你做改动的时候可以自动精简文件(给你节省了很多工作!)。

有人可能会说,使用Gzip就足以降低文件文本的传输成本。例如,精简虽然可以把文件缩小10%至15%,但是精简过和没精简过的文件进行Gzip压缩,所节省的文件大小差异只有1%至2%(因为只有空格的压缩效果好)。

可是,即便只能节省1%至2%的网络传输量你也应该养成精简的习惯,因为这样做能为你的客户节约设备存储空间。另外,文件越小读入内存的速度就越快(而且导致内存有限的设备崩溃的可能性越小)。

虽然Sounders的原则只考虑到精简JavaScript,实际上这个优化策略可以用于缩小任意文本文件。ARO工具把所有的CSS,JavaScript,JSON和HTML文件都考虑在精简范围内(见图7-12)。它不仅把每个文件的潜在节省量计算了出来,而且还统计出了跟踪中捕获的所有文件所能达到的总节省量。

图7-12 ARO精简最佳实践

图片

图片是应用里最常见的下载文件类型,同时也是最大的一种文件形式。它无处不在,可以很容易从网页或者其他数字服务中获取。控制图片大小可以有效减少手机应用的数据流量。你必须为你的应用在图片质量和图片尺寸之间找到一个平衡(可以咨询UX或编辑团队)。一旦找到了正确的平衡点,你就拥有了一个美观的应用,优化后的图片可以很快下载和呈现。

尺寸超大化?

如果你的应用里每张图片只有一个版本,要将它用于所有移动设备(包括使用其他移动操作系统的视网膜显示屏平板电脑),那么你可能要使用一张在所有设备上看起来都很清晰的图片(这意味着下载的图片非常大)。现在想象一下,把适用于视网膜显示屏平板电脑上的一张图片通过2G网络发送到小小的安卓设备上需要多长时间,你就会明白这不是你想要的用户交互。

为了适用于各种不同尺寸的屏幕,你可能想要为你的图片创建图片bucket。这样在需要一张图片时,App就可以提供屏幕尺寸以确定传递正确尺寸的图片。 安卓为应用开发提供了屏幕分辨率bucket,这些值对网络图片而言也是一个良好的开端。

如果缩略图和原图在App上的展示效果一致,就可以考虑下载缩略图而不是原图。

Dumbnails

我以前用过一款流行应用,它的每篇文章旁边都有一个250KB的缩略图。一个页面上有六到八个文章标题,每次启动应用的时候,这些小图片就要增加1.5-2MB的数据载入。由于尺寸原因,开发者把它们戏称为“dumbnails”,在后来发布的版本中采用了5-10KB的小图片代替。

你可以选择的图片尺寸很大程度上并不是由应用决定的,而是根据布局得知屏幕上必须显示多大的图片。因此,只有先根据中小型平板设备和手机的屏幕大小计算出最合适的像素尺寸,才可以为应用创建正确的图片bucket。

元数据

如果你用数码相机拍一张照片,文件里很可能有与照片相关的元数据(包括设备名称、设备设置以及拍摄地点等信息)。照片编辑软件也会给图片增加一些元数据。除非你的应用是讨论照片拍摄方法和编辑技巧的摄影应用,否则可以去掉照片上所有相关的元数据,将这些几字节到几十千字节不等的元数据保存在其他地方,这样不会损失发送给用户的图片的质量。

压缩

跟文本文件一样,你也可以压缩图片让它们变小,从而减少占用的设备存储空间,同时还能缩短下载时间。图片压缩这个话题太大,在这里不便详述,但是必须指出,在压缩图片的同时,图片质量也大幅下降了(有损压缩)。

适用于图片的有损压缩率取决于图片的用途。对于缩略图,压缩率可以更高,因为它们本身就很小,很难发现有粒子或像素变化。对于文章中的图片,以70%的压缩率保存JPEG就足够了。专注于摄影与图形的应用,可以选择不做任何有损压缩。压缩率是图形质量优化与为速度压缩尺寸之间的一种微妙平衡。 Google的PageSpeed服务器默认的图片压缩率是85%,这可能是图片对比的一个好的起点。

WebP:JPEG的接替者?

WebP是Google正在开发的一个图片格式。WebP格式图片通常比类似的JPEG格式图片小20%。支持WebP的浏览器和设备日益增多(安卓4.0及以后版本支持)。降低图片文件大小,WebP值得考虑。

文件缓存

如果应用中存在经常使用的文件,那么你应该一次性下载这些文件并在本地保存以供多次使用。因为就性能而言,本地读取文件往往比建立连接并下载文件更快。单凭这一点,缓存就可以加速应用的呈现。减少网络连接不仅能节约服务器容量,还能降低用户的电量消耗。

当然,调用缓存的主要原因是移动数据使用受流量套餐限制,下载过多内容可能使用户超出每月流量,多花钱。缓存有两个要点:首先,必须在设备上的应用内打开缓存功能,其次还必须在服务器端设置正确的缓存时间。

应用内缓存

有意思的是,安卓系统缓存功能默认是关闭的,所以你需要把它打开。对于安卓4.0及以上版本,在onCreate中调用以下代码就可以启用HTTP响应缓存:

private void enableHttpResponseCache() {
  try {
    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
    File httpCacheDir = new File(getCacheDir(), "http");
    Class.forName("android.net.http.HttpResponseCache")
         .getMethod("install", File.class, long.class)
         .invoke(null, httpCacheDir, httpCacheSize);
  } catch (Exception httpResponseCacheNotAvailable) {
    Log.d(TAG, "HTTP response cache is unavailable.");
  }
}

现在你的应用可以缓存了。

服务器端缓存

服务器向设备传输的报头设定了设备里存储的每个文件的缓存时间。在服务器上设置缓存参数时,必须考虑几个重要因素。通常,文件缓存有一个有效期,应用在有效期内请求文件,由设备缓存提供该文件。如果缓存过期,则连接服务器,检查文件是否被修改。如果文件相同,则向设备发送一个HTTP 304 “not modified”响应,并重置缓存定时器;如果文件不同,则下载新文件。

缓存定时器的长度实际取决于缓存内容及其改动的频率(例如,运动队标志这些很少改动的可以缓存一年,天气情况缓存5分钟,头条新闻订阅源可能不缓存)。更改内容的缓存时间不仅可以确保用户看到的数据始终是最新的,还能限制重复下载的文件的数量,从而节省电量和流量。一般来说,有三种报头可以用来设置内容的过期日期。

Cache control(添加Expires报头)

缓存最常用的报头是Cache-Control报头,它有几个可以指定的常用值:

Private/Public

网络中CDN缓存使用的代表性指令。它告诉CDN文件是公共的(任何人都可以使用)还是用户的私有文件。

no-store

如果文件使用该指令,那么无法缓存该文件,必须每次都下载。

no-cache

no-cache报头的名称可能令人误解。带有no-cache报头的文件实际上是可以缓存的,但是再次使用前必须重新验证。

max age=X

max-age表示文件可以缓存的最大时间(单位秒)。常用值为0(与no-cache相同)、60、300、600(1小时)、86400(1天)、3153600(1年)。

ETag

ETag是一种响应报头,包含由随机字符组成的唯一字符串。每次从缓存中使用文件时,ETag必须首先在服务器端验证。如果本地字符串与服务器端一致,服务器发回“304 not modified”,使用本地文件。如果ETag不一致,则下载新文件并保存在缓存内。它的作用与Cache-Control: no-cache或max-age=0相同。

对于经常过期的文件,ETag是一个验证本地缓存文件是否依旧与服务器同步的好方法。但对于很少改动的文件,ETag则是一个昂贵(从性能方面来看)的缓存机制。因为尽管并未下载文件(这样节省了带宽),但是依旧建立了连接,连接时间延长了文件处理过程。

在下例中,有一个ETag报头和一个Cache-Control报头。在86400s(1天)时间内,设备会从缓存读取文件,之后检查ETag(或最后修改)报头,查看文件是否被改动。如果无改动,则继续使用缓存86400s:

图略

Expires

Expires报头不如Cache-Control和ETag报头常见(但同样有效),它并不给出按秒计的文件过期时间,而是给出文件过期和应当重新验证的未来具体日期。这是网络上最早使用的缓存报头,一些古老的浏览器可能还在使用它。Expires报头应与Cache-Cotrol: max-age一致。在前例中,Expires报头恰好为从服务器获得文件的一天后。

比缓存更快?

你的应用会在第一次启动时下载内容并缓存很长时间?记住:第一次启动几乎决定了用户满意度,关乎成败。如果你的应用第一次启动花费了很长时间进行配置(下载图片和文件),那么用户可能就不会再用你的应用了。把图片和文件放入App的资源文件,虽然需要下载的App会变大,但是可以加快第一次启动的速度。此外,如果你改动了标志、图标等,你需要做的是发布一个应用更新。

你可以使用ARO查看你的应用是否正确缓存。有三个最佳实践可以帮助你确定缓存文件的问题:重复内容、缓存控制和内容过期。图7-7中的表格显示了一个跟踪里下载超过一次的文件列表、每个文件的下载次数和重复文件的大小。ARO里的Cache Control(缓存控制)和Content Expiration(内容过期)最佳实践充当的是服务器端和设备端(各自)潜在缓存问题的警告。

ARO Cache Control(缓存控制)最佳实践查找有无Cache-Control/ETag或Expires报头。如果服务器未插入此类报头,则发出警告“此处缓存策略可能失败!”可能有的文件你不想缓存,所以就省去了报头。但是这里有一个重要问题需要注意:HTTP缓存规范规定如果文件不含内容,那么会被缓存24小时。如果你不想缓存文件,必须明确说明,避免出现按照规范进行缓存的情况!

ARO Content Expiration(内容过期)最佳实践确保应用缓存正常工作。它计算“304 not modified”服务器检查消息的数量、缓存报头被忽略的次数和向服务器请求文件的次数(当文件应当在缓存中时)。通常,它会在设备上未配置(或未正确配置)缓存的应用上发出一个警告。

如果你的应用重复下载内容,要多加注意,确保正确插入报头(服务器修复),以及应用在缓存里正确地保存文件(应用修复)。

文件之外

优化下载的文件很重要。更小更精的文件常常可以使下载更快,性能更高效。但是,现在你也知道蜂窝网络延迟和176页”RRC 状态机“是如何影响下载速度和用电量的了。假设所有文件都已经经过优化,现在我们需要确保用来连接应用和服务器以获取这些文件的过程尽可能的高效,与RRC状态机协作,最大化性能和用户满意度。

分组连接

想象一个广告支持的图片分享应用。直觉上我们知道传输图片的连接会占用很大带宽,并且会经常连接网络。你知道广告SDK的行为吗?广告是不是和图片一起载入的,当无线静默的时候也会发生这些连接吗?你的分析数据又怎么样?这些连接是同时发生的吗?这些连接在任何时候都可以随心所欲地唤醒无线吗?

你可能会想,构建这些工具就是为了连接。如果除了库和连接外,你还使用了多个其他的分析提供程序(或广告服务),那么你的应用就永远也不会让无线休眠了,因为所有的服务想什么时候连接就什么时候连接。如果想让应用尽可能高效,将连接编组为几个大的bucket(而不是很多个小的bucket)是可行的。查看这些SDK的文档和代码,看看是否可以将它们与应用里的其他连接同步。如果经过测试发现第三方SDK的表现达不到其应有的水平,联系它的开发者。开发者可能也不清楚库的表现,并且可能会有兴趣改善库的网络表现。

定期连接

由于RRC状态机,每个服务器连接都会使无线保持在活动状态,所以尽可能减少应用内的(特别是后台发生的)连接数量非常重要,这样不仅能延长电池寿命,也能减少用户的流量。2013年,我的团队与一个流行社交媒体应用合作,目的是减少安卓客户端应用在后台产生的连接数量。在这个应用的早期版本里,我们看到有三个连接以不协调的方式在后台运行。每30分钟,这三个连接打开无线七次。在图7-13中,这30分钟以红色”Bursts(突发)“的数据包为界线。在图片上部,你可以看到两个发生时间非常接近(但不重叠)的紫色、黄色和蓝色突发。紫色连接打开第二和第三个连接,黄色突发再次使用这两个连接,蓝色突发是服务器发出的数据包,告诉应用关闭连接。

图7-13 社交媒体后台连接:优化前(上部)和优化后(下部)

开发人员看到这个后就知道,通过简单地协调这些连接(并确保正确关闭它们),可以大大减少应用的后台电量消耗。下部的跟踪显示了改进情况。应用的”升级“版将这三个连接整合到一个事务时间,刷新率被开发人员加倍至每15分钟。现在,每30分钟连接2次,数据更新频率是原来的两倍,而用电量却居然下降了>50%。假设这些连接一天24时候都发生,我们估计这样可以为每个安装了这个应用的用户实际节省~5%电池电量。

并不是所有开发人员都有时间制作自己的事务管理器,但是安卓的开发人员一直在注意这个问题。在66页的”JobScheduler“里,我们讨论了安卓5.0引入的JobScheduler API,举例说明了如何让操作系统减少周期性连接,从原先可能多达20个连接减少为9个。通过将JobScheduler API的灵活性加入到下载非关键性元素中,并在后台连接加入回退机制,将大大降低无线使用情况,并能提高应用性能,同时降低用电量(你和用户的双赢)!

检测应用的无线使用情况

要确定用户设备连接的是WiFi还是蜂窝网络,你可以查询连接管理器,如例7-1所示。

例7-1:连接识别:WiFi或蜂窝网络

public static String getNetworkClass(Context context) { 
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();

if(info==null || !info.isConnected()) 
    return "-"; //not connected
if(info.getType() == ConnectivityManager.TYPE_WIFI)
    return “wifi";
if(info.getType() == ConnectivityManager.TYPE_MOBILE)
     return “cellular";


    return “unknown"; 
}

通过这个数据片段,你可以知道使用的是哪种连接,你可以针对这两种网络类型定制数据流。如果用户使用的是蜂窝网络,可以使用能推迟传输的非紧急通信。在安卓5.0的JobScheduler出现前,无法知道网络是否在使用。使用例7-2中所示的代码,可以让分析和广告只能在蜂窝网络已经在使用的情况下载入。

用例7-2:确定蜂窝网络连接是否存在

if (Tel.getDataActivity() >0){
    if (Tel.getDataActivity() <4){
    }
}
//1, 2, 3 response means that the cellular radio is transmitting!
//download the image here using image getter
imagegetter(counter, numberofimages);
//and show the ad
AdRequest adRequest = new AdRequest();      adRequest.addTestDevice(AdRequest.TEST_EMULATOR); adView.loadAd(adRequest);
// Initiate a generic request to load it with an ad 
adView.loadAd(new   AdRequest());

这个代码片段使用TelephonyManager(Tel)数据行为API确定无线是否打开,如果无线已经打开,则使用这个连接下载更多内容。它只表明数据传输是不是在蜂窝网络(不是WiFi)上发生的。在5.0中,ConncetivityManager中加入了新API,现在可以利用ConnectivityManager将这个仅适用蜂窝网络的方法推广应用到所有无线连接。OnNetworkActiveListener可以找出无线网络何时处于高功率状态(并准备传输数据)。可以使用ConnectivityManager.isDefaultNetworkActive()查看网络是否已经处于活动状态。使用已经建立的无线连接是共享资源和节省用户电量的好方法。

GCM Network Manager

2015谷歌 I/O大会上,谷歌和安卓使省电的网络连接调度更为简单。作为Google Play服务的一部分,它们模仿JobScheduler API 连接添加了GCM Network Manager API。但是JobScheduler只能在使用Lollipop的设备上运行,而GCM Network Manager可以在Gingerbread(2.3)版本之后的所有谷歌安卓设备上运行。 现在,和在JobScheduler中一样,你可以简单地设置连接,只在Wi-Fi开启状态或设备连通电源的时候运行。你也可以设置任务在后台定期运行或自动退出。 利用此API进行非紧急性更新与连接,你可以为客户直接节省大量的设备电池用量。

适时关闭连接

在手机上建立无线和TCP连接发生延迟,你可能会认为明智的做法是只对服务器开启TCP连接。 这样一来,如果更多的数据包需要发送到设备,可以减少一些连接设置延迟。对相对快速连续发送的文件确实如此。但如果文件发送的间隔时间达到或超过了15秒,可能还是得开启无线(其实连接设置所需时间极少)

如果一段时间内没有数据流量而连接还在开启,设备和服务器都有个关闭连接的清理进程。这也不是件坏事(下一节中讲述原由)。但缺点就是关闭连接的一方要告诉对方:“喂,我现在要关闭连接了”,这可能会造成无线连接的继续开启,在设备上运行10~15秒的RRC状态机,给客户造成额外的电量损耗。

从图7-14 ARO展示的快照来看,在第8秒的时候一个小图片进行了加载,但是连接并没有断开。

图 7-14. ARO 诊断选项卡显示连接关闭问题

在第18秒的时候,服务器(很可能在执行清理进程)关闭了连接,造成RRC定时器重置(在数据包查看表中:18秒时服务器数据ID24关闭连接)。但无线连接并没有在18~19秒的时候关闭,而是持续开启到28秒——几乎耗费了下载一张图片的两倍电量。

对于那些你知道不再需要的连接,可以在下载结束时指定关闭。例7-3中,我禁用了“保持连接”。然后下载结束时,我就断开连接。这就告诉安卓系统连接资源可以重复使用或者关闭(节约内存等)。

例 7-3. 适时关闭连接

HttpURLConnection connectionCloseProperly = (HttpURLConnection)ulrn.openConnection(); 
//this disables "keep-alive"
connectionCloseProperly.setRequestProperty("connection", "close");  connectionCloseProperly.setUseCaches(true);
connectionCloseProperly.connect();
Object response = connectionCloseProperly.getContent();
InputStream isclose = connectionCloseProperly.getInputStream();
    ...download and render bitmap image
connectionCloseProperly.disconnect();

一旦执行这段代码,图片下载完后服务器和设备立即关闭连接。

定期执行重复的Ping命令

那些需要定期更新数据的应用,应使用Google Cloud Messenger之类的工具向应用推送更新信息。你可以定制个性化服务,通过设置提醒每x分钟后开始后台运行,唤醒无线连接并下载数据。这似乎不是什么大事,但是想象一下,应用每三个分钟就Ping一次服务器获取更新信息,可以推算出你的应用每24小时就要连接480次。置入一个10秒的状态机定时器,这些“无害”连接每天要耗费80分钟进行无线连接。如果必须定期唤醒无线连接获取数据,你要确保可执行回滚操作,或在特定时间段后禁用提醒以保持应用(或设备)休眠。

有些情况(想想实时游戏)应用可能需要保持连接上的数据包交换。在这种情况下,务必将发送的数据量减到最少,而且要明白应用程序运行时保持无线连接开启是非常耗电的。

完美风暴:重复连接和关闭连接

想象一下,当几个人在某区域移动,他们手机上的应用每隔五秒和服务器交换一次实时数据来更新位置信息。再想象一下,收到最后一个数据包后(在服务器上保留IP地址)服务器上仍然保持连接90秒。通常而言,每个用户每次会话都要占用一个连接,这应该不是问题。但是如果你更改了代码中配置,使每一次ping都与服务器建立一个新的TCP连接,而应用在发布前没有测试到这个问题,最后会发生什么事?

你引发了一场完美的数据流量风暴!现在每个安卓用户每五秒ping一次,就要用到18个之多的IP地址连入你的服务器。连入的用户越多,你就会发现IP冲突,导致用户无法连接!恭喜,你的应用向服务器成功地完成了一次DDoS攻击。

因此,要特别注意服务器重复执行ping操作,发布应用前应当测试。

网络安全技术的应用(HTTP和HTTPS)

在用网络传输数据时,必须保证客户的个人数据安全。似乎每周都爆出一款移动应用存在严重的个人信息安全漏洞。在本地设备和服务器上恰当存储文件非常关键,但是在网络上正确传送文件也很重要。你的客户可能会连接到任何类型的网络,包括服务场所不安全的Wi-Fi热点。如果你传输的数据是通过HTTP发送,窥探者毫不费力就能获取该数据——因为你是用明文发送的!你应该通过HTTPS,使用秘钥加密数据,然后再将秘钥告知接收方,虽然初始化连接会造成额外的数据往返,但是你只要正确配置了HTTPS连接,这个传输过程就是安全的。

7.4 全球移动网络覆盖范围

关于好房子,地产中介有句口头禅:“位置,位置,位置。”如果你按照刚刚所说的那些最佳实践去彻底优化你的网络,那么你在移动网络性能上就迈出了漂亮的第一步。然而,尚有一项最重要的变量我们没有考虑,那就是用户的网络速度。(很显然)我们不能控制用户以什么方式或在什么地方连接到我们的App,不过,我们可以努力地尽量优化体验。

据GSM协会信息,2014年,近40%的移动网络连接设备是智能手机(至2020年 ,此比例会上升至65%)。

如图7-15所示的全球移动网络市场渗透率,2014年第三季度,4G的市场渗透率大约是5%,3G略微超过30%。这意味着,有相当大一部分用户(至少5%,如果我们只考虑不具备3G功能的手机)使用专用2G网络的智能手机。

图7-15.全球“G”市场占比(GSM协会供图)

2013年,百度宣称中国有2.7亿的Android用户,而其中31%使用2G网络连接。从那以后,LTE在中国推出,但我们还是很明显地看到,很多Android用户仍然只使用2G网络进行数据连接。智能手机使用慢速网络并不只是美国和欧洲以外地区才存在的问题。即使在发达国家,有些地方也并没有开始推行LTE(或高速网络只有零星覆盖)。用户将可能去到这些地方并使用App,因此,有必要确保移动App在慢速和更拥挤的网络中运行良好。

在第二章中,我在第14页探讨了为何“你的设备不是你用户的设备”。同样 ,你的移动网络不是用户的移动网络。大部分的开发者处在高速网络覆盖的区域,至少是3G(很有可能是4G LTE)无线连接。我们开发者除了拥有大屏幕和快速的处理器之外,还生活在高可用性网络的良好环境中。对于和我们有相同环境的用户来说,这当然很好。然而,了解世界各地的网络连接才可以确保我们的确在为最终用户提供正确的数据。

CDN服务器

由于延迟是移动数据通信的一个主要的绊脚石,任何能减少延迟的事情对最终用户来说都将加速数据的获取和App的渲染。虽然光的速度快得令人难以置信,但它也需要花53ms从波士顿往返于伦敦(从波士顿到悉尼需要162ms)!为了减少延迟,应当考虑使用内容分发网络在世界各地的数据中心制作内容的镜像,从而让用户可以更快地获取到他们需要的数据。

CDN服务器(大体上来说)是用来在网络前沿或者接近最后一公里处存储数据的服务器。它依赖于一个数据存储的分布式系统,主系统不会因为请求太多而不堪重负。将这些CDN服务器放置在用户附近,数据就更靠近用户了,这样就减少了请求和传送文件的往返时间。

根据Facebook报告显示,在印度尼西亚,50%的手机用户使用Facebook,而其中75%的用户使用2G网络。在连接有限的情况下,面对大量用户,Facebook尝试尽可能实现收益最大化,印度尼西亚是一个大群岛,各地距离很远,而该国大部分地区距离新加坡(一个可能的CDN位置)3200公里。数据从本地CDN服务器到用户手机在光纤中的往返时间大约需要32ms(假设光在光纤中的速度为200000km/s)。即使有这样大的延迟,Facebook发现还是需要积极地进行CDN映射。通过分析,Facebook发现,仅仅16%的流量来自本地CDN服务器,而84%的图像或者视频来自南美(跨越半个地球)。对于从南美传输到印度尼西亚的数据来说,它必须要跨越太平洋。哪怕我们将CDN服务器放置在厄瓜多尔(南美洲的最西端),你的数据仍然必须传输大约18000公里,176ms的往返时间(是来自新加坡CDN服务器的5.5倍)。CDN的价值很明显地体现了出来,而且精心调节CDN流量以缩短数据传送给用户的距离也很重要。

在慢速网络中测试你的应用程序

测试App在慢速网络中的表现,第一步应该是申请一次环球旅行,去到App会被用到的地方(这没有什么大不了的,对吧?)。从上面章节中可知,Facebook已经在非洲和印度尼西亚进行了测试,并且公布了一些很有趣的结果。在非洲,通过一个月的数据统计,Facebook发现他的App在40分钟内会崩溃。这次统计促使Facebook竭尽所能的去减少数据使用量和网络利用率,并且去优化图像和做更好的缓存,最后Facebook的数据使用量减少了一半(获得了所有用户的一致好评)。

很少有公司具备和Facebook一样的资源条件,因此没法通过环球旅行来做App的测试自然是可以理解的。然而,仔细分析调研数据,通过对各地区、各国家的延迟时间和带宽问题进行挖掘,还是可以得到一些有用信息的。

仿真慢速网络而不用倾家荡产

在第二章中,建议使用专用的Wi-Fi网络进行数据测试。如果设备实验室在高速网络下进行所有的测试,那么就有可能漏掉慢速网络下的重要的测试情景 。如果不考虑移动网络吞吐量的差异性,将有可能难以吸引新用户并且这会降低那些去到差网络环境下的现有用户使用的积极性。运营商会在一个封闭的环境中使用专门的天线去测试各种情况。但是搭建这些环境是非常昂贵的,那么怎样才能很好的进行测试而又不用倾家荡产呢?一起看看以下这些方法吧(按实现的成本列出)。

无线网络节流

如果你正在使用无线路由器进行测试,并且可以在路由器上安装OpenWRT (一个开源路由器),那么会有一个wshaper插件让你可以对上行和下行链路连接进行节制,这至少让你可以模拟较慢的网络速度(但不是延迟)。

模拟器

安卓模拟器可以控制网络条件。打开模拟器后,你可以登录到模拟器来模拟 不同的吞吐量和延迟:

telnet localhost 5554
network speed edge  //gprs, umts hsdpa和全面的附加选项                      
network delay edge

自制法拉第笼

法拉第笼是一个由金属丝构成的笼子,它将内部空间同外部的电磁辐射完全隔离开来。通过制作一个局部法拉第笼,你能够减少到达手机的信号量。已经有 报道称,一些开发人员成功使用旧的(不插电!)微波炉屏蔽掉了部分无线电。 由于实验的不确定性,这些测试的结果可能是难以重现的,但这也许已足以用于定性测试了。

网络弱化器

AT&T发布了一款名为AT&T网络弱化器的工具,如图7-16。这款网络弱化器在 三星S3 ICS内核上运行(需要root和一个由AT&T提供的自制ROM的闪光)。安装后,这个App可以像一个刻度盘一样减慢移动网络、降低吞吐量(很抱歉,如果你连的是3G网络,它并不能使你的连接变得和4G一样快)。当你滑动滑块将网速从UMTS变为EDGE时,上行链路,下行链路和往返时间定时器都会相应的做出调整,让你在较慢速的网络条件下对App进行一些简单的测试。你也可以从左到右滑动滑块来的调整你的网络拥塞程度,增加每个连接的往返时间。

图7-16.网络弱化器APK

构建网络感知应用程序

如果你知道用户将会连接到不理想的网络(而且我们知道他们肯定会),那么保证他们的最佳用户体验难道不是理所应当的吗?我不是建议你降低App在3G或者4G下使用的质量,而是有技巧的加强App在慢速网络下的体验。我喜欢称这些运用了这种架构的App为“灵活的网络感知”(FNA)App,因为它们可以感知网络,并且根据所测得的网络条件来调整用户的体验。下面一起来看一下“网络活动示例”App的代码。在描述App时大声的说出运用了FNA技术是一件很酷的事情。

要为连接快速、中速和慢速网络的设备提供不一样的移动体验 ,只需要简单的去除内嵌视频或者减少图像的数量(或者至少改变图像的大小)。示例7-1的代码是用来进行移动网络连接的,你可以查看 TelephonyManager类。你能像示例7-4那样,使用App来转变展示体验的类型 。 例 7-4 确定移动网络速度

TelephonyManager teleMan = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
int networkType = teleMan.getNetworkType();
switch (networkType){
    case 1: netType = "GPRS";
        networkSpeed = "slow";
        break; 
    case 2:  netType = "EDGE";
        networkSpeed = "slow"; 
        break;
    case 3:  netType = "UMTS";
        networkSpeed = "medium";
        break;
// we'll leave out a few network types, but you get the idea.
//You can see the full code on GitHub
    case 13: netType = "LTE";
        networkSpeed = "fast";
        break;
}

通过网络状态的定期检查,FNA App将会根据当前的网络情况适当的增强或者减弱性能。这是一个非常简单的算法,并且不需要考虑网络的强度。你可以进一步的认为弱的3G网络是慢速网络,或者弱的4G网络是中速网络。

例如,你可以用一个App在慢网上下载一张小图片,中速网络上下载一张中等大小的图片,而在快网上下载一张大图。和前面类似,你可以认为Wi-Fi是快速网络,或者,甚至是“高速”网络,只是通过Wi-Fi向用户发送数据并不会产生流量费用罢了。

例7-5.设定网速

switch(networkSpeed){
    case "fast":
        new ImageDownloader().execute(urlbig); //image is 143KB
        break; 
    case "medium":
        new ImageDownloader().execute(urlmed); //image is 41KB
        break; 
    case "slow":
        new ImageDownloader().execute(urlsmall); //image is 27KB
        break; 
}

下载图片的时候,我计算了下载的实际耗时,为了计算更准确,我记录了两次:从发送请求到收到来自服务器的200回复的时间,以及下载图片的总用时。响应时间(收到服务器的200回复的时间)是往返时间的两倍(这里假设DNS查找已提前完成),它可以用来估算网络延迟。下载时间是从服务器接收到对象的总用时。另外通过查询内容的长度,我的Android App能够计算出以KB/s为单位的该文件的实际吞吐量。

例7-6.确定实时的往返时间和吞吐量

private Bitmap downloadBitmap(String url) {
    Long start = System.currentTimeMillis(); //download start time

    final DefaultHttpClient client = new DefaultHttpClient(); 
    final HttpGet getRequest = new HttpGet(url);
    try {
       HttpResponse response = client.execute(getRequest);
       //check 200 OK for success
       final int statusCode =                                   response.getStatusLine().getStatusCode(); //time 200 response received
       Long gotresponse = System.currentTimeMillis();
     }
     final HttpEntity entity = response.getEntity(); //get ContentLength of file
     contentlength = entity.getContentLength(); 
     if (entity != null) {
         InputStream inputStream = null;
         try {
            inputStream = entity.getContent();
            final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
            Long gotimage = System.currentTimeMillis();
            //time image download completed
            responsetime = gotresponse - start;
            //time to the 200 ok response
            imagetime = gotimage-start;
            //download time
            throughput= ((double)contentlength/1024)/       ((double)imagetime/1000); //KB/s
            return bitmap;
         }
     }
}

那么,这些数据告诉了我们什么呢?用网络弱化器App来模拟各种不同网络速度,能够计算出上述三种不同网络环境下三种不同大小文件的下载时间,结果如表7-2。

表7-2.图片下载时间

如果所有网络条件下都使用大文件(一个非FNA App),那么很显然,由于文件较大,使用UTMS和EDGE的用户体验会明显较慢。如果运用FNA框架,UMTS的下载速度会快将近一倍,而EDGE会快将近三倍。然而不可否认的是,尽管这个简单的模型显示了它在改善用户交互方面的优势,但使用网络技术来判断下载速度从而估算理想网络速度是一个非常粗糙的方式。

在一个顶级的网络环境中收集延迟和吞吐量数据能帮你构建一个更好的算法,使你的App可以根据接近实时的网络环境灵活的进行调整,但很显然的是简单的算法对用户更有益。

测量延迟

据我所知,测量RTT有很多的可变因素。到蜂窝基站的距离,拥塞,或者是来自于其它无线电的干扰都能引起RTT(译者注:往返时间)的剧变。因此,不能仅依赖于一个或者两个离散的测量值,而应该先去掉任何潜在的异常值后取测量的平均值。虽然根据当前的网络状况进行运作是很棒的,但平稳的输出数据也是至关重要的。

计算延迟

如果你的FNA App感知到你的用户正处于一个高延迟的环境当中(根据计算得到的高RTT),它能够决定通过更积极的预加载帮助用户加速体验。 例如,如果用户正在浏览一系列的图片,你可以在用户到达列表尾端之前先对其它图片进行下载(例如,屏幕上只有两张图片,开始下载下一屏图片):

 if (ImagesBelowtheFold<2){
            <get next batch of images>
 }

在高延迟的环境中,用户可能在下一屏的图片加载之前已滑到了列表的末尾。考虑到这种情况,你可能会提前去获取下一屏的图片:

 If (latency = normal){
       if (ImagesBelowtheFold<2){
              <get next batch of images>
        } 
 }
 Else {
       //latency is high
       if (ImagesBelowtheFold<4){
              <get next batch of images>
       //consider getting more images too
       //also, smaller images?
 }

通过将开始下载的时间提前两倍,你相当于在用户注意到延迟之前给了网络两倍于之前的时间来下载数据。 这样可能占用网络的时间稍长,并有可能下载了更多的图片(使用更多的数据),所以它需要结合情境使用。但是如果能让用户得到无缝体验,这样做也许是值得的。

最后一公里的延迟

延迟通常在数据传输的最后一公里发生,而在移动网络中尤其是这样。这些技巧可以帮助你应对延迟,但它们仅仅只是缓解问题,而不能从根本上解决它。 正如我在205页“Testing Your App on Slow Networks”中描述的那样,Facebook发现在印度尼西亚,84%的慢网连接流量来自于南美和欧洲的CDN服务器。

"其它"无线电

Wifi和蜂窝无线网都是最常用的传输数据的网络,也是最简单最易优化的。但是其他的无线电网络也会导致设备的能耗,因此它们的行为也应该被关注讨论。

GPS

Android提供了模糊定位,使用附近蜂窝基站和Wi-Fi网点的信息可以生成粗略的位置信息,并不需要开启GPS。然而,很多App需要更精准的定位,它们会打开GPS设备,从GPS卫星接收信号。定位需要从你的手机到卫星之间的一条信号线。

为了优化定位设备的使用性能,你可能必须调整GPS打开的时间(保持GPS接收器打开多久),以及使用频率。打开的时间越长,使用的频率越高,你得到的位置信息就更精准。

蓝牙

目前,所有的Android旧设备都必须通过蓝牙才能连接到其它设备。如果你对蓝牙的数据传输感兴趣的话,你可以在Wireshark的非集群中收集它的日志信息。 对于使用KitKat和更新版本系统的设备,你可以在开发者选项设置中打开“Bluetooth HCI snoop log”。当你选中了该选项,Android设备将会收集所有通过蓝牙接口发送的数据包的日志信息,信息会被存放在/sdcard/btsnoop_hci.log中。

在Wireshark中打开这个日志文件,可以看到被传输数据包的信息。大部分数据是加密的,但是你可以观察到两个设备之间的通信模式(图7-17)。

图7-17.Wireshark中的蓝牙通信

在这个例子中,请求的响应是一个POST,你可以查看我在谷歌查询“澳大利亚牧羊犬”(来自Glass)的响应信息(图7-18)。

图7-18.蓝牙POST响应

使用Wireshark工具,可以看到一段时间内蓝牙传输的数据包的数量(图7-19)。

图7-19.蓝牙POST响应-包的数量随着时间的变化

总结

大多数的Android App使用蜂窝和Wi-Fi无线网来和外部服务器进行数据通信。因为无线电设备是仅次于屏幕耗电量的第二大耗电设备,因此使用它们的时候需要非常谨慎。此外,大多数用户每个月的流量是有限的,那么在移动网络传输文件时,在速度和流量消耗上选择更优质的策略则显得更为重要了。除了考虑数据流量的成本之外,也必须考虑高延迟和较慢的网络速度。无论用户在哪,无论当前的网络环境怎样,确保你的数据传输对当前的网络环境来说是优质的将会使你的App脱颖而出。 通过RCC状态机来优化你的数据流量和用户的位置信息将会延长手机电池的使用寿命并且增强所有用户的体验。

 posted on 2016-05-19 23:18  wlrhnh  阅读(358)  评论(0)    收藏  举报