【Android Developers Training】 83. 实现高效网络访问来优化下载
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接:http://developer.android.com/training/efficient-downloads/efficient-network-access.html
在你的应用中,可能最消耗电力的就是用无线网络对资源进行下载了。 为了最小化网络连接对电池损耗的影响,你一定要理解你的连接模型是如何影响底层的的无线硬件的。这节课将介绍“无线网络状态机”并解释你的应用连接模型如何与这个状态机联系起来。紧接着我们会介绍如何减少你的数据连接,使用预取(use prefetching)和捆绑传输(bundle)让数据传输对电池寿命的影响降低到最低。
一). 无线状态机
一个完全活跃的无线连接会显著地消耗电量,所以它会在不同的能耗状态间切换,这样做的目的是在它没有被使用时可以节省电量,同时又能保证可以在尽量短的时间内重新启动无线电。
对于一个标准的3G无线网络来说,它的状态机由三个能耗状态组成:
- 高能耗:当一个连接处于活跃状态时使用,允许设备在其最高的传输速率下传输数据。
- 低能耗:一个中间的状态,其功率损耗大致为高能耗状态的50%。
- 待机:最低的能耗状态,这种情况下没有活跃的或者需要的网络连接。
虽然低能耗及待机状态会显著减少电量的消耗,但他们也会在网络连接请求发起时导致巨大的延迟。从低能耗状态回到高能耗状态需要大约1.5秒,而从待机状态回到高能耗状态则需要大约2秒甚至更多的时间。
为了将延迟最小化,状态机使用了一个延迟机制,来推迟由高能耗到低能耗这一状态切换。图1所示的是AT&T对于标准3G网络的状态机时间参数:
图1. 标准的3G无线网络状态机
每个设备上的无线状态机,尤其是状态切换的延迟时间(尾时间“tail time”)以及启动时间会随着所采用的无线技术的不同而变化(2G,3G,LTE等),这些参数都由设备所支持的网络运营商所定义和配置。
这节课将会讲解一个具有代表性的标准3G网络无线状态机,它基于AT&T所提供的数据。然而,这一思想及实践方法对于所有的无线网络的实现来说也是可以应用的。
这里所使用的这一状态机对于网页浏览来说是非常有效的,因为它不会在浏览网页时引入用户无法接受的长延迟。较短的状态切换时间也可以是的一旦一个浏览网页的会话完毕了,无线网络会转换到低能耗状态。
不幸的是,这种方法会导致一个现代智能手机操作系统上(如Android)的低效应用,因为应用即在前台运行(控制延迟异常重要),也在后台运行(对电池寿命的影响)。
二). 应用如何影响无线状态机
每次你创建一个新的网络连接,都会切换到高能耗状态。在上面所说的标准3G的无线状态机而言,它在高能耗状态所维持的时间为:数据传输时间+额外的5秒切换延迟,紧接着是低能耗状态下的12秒切换延迟。也就是说,对于一个典型的3G设备,每次数据传输会话将会导致无线电消耗电量的时间长达20秒左右。
在实际情况下,这意味着一个应用如果不把要传输的数据打包,并每隔18秒传输1秒的数据,那么就会然无线电一直处于活跃状态,在它即将回到待机状态时,又回到了高能耗状态。它导致的结果就是:每分钟内,会以高能耗状态消耗电量18秒,剩下的42秒为低能耗状态消耗电量的时间。
作为对比,同样一个应用如果将要传输的数据打包,每分钟传输3秒的数据,那么高能耗状态只保持8秒,低能耗状态只保持12秒。
上述第二个例子允许无线电在每分钟内有40秒处于待机状态,这回显著减少电量的消耗。
图2. 打包传输数据和不打包传输数据时,无线电的电量使用对比
三). 预取数据
预取数据是一种非常有效的减少独立数据传输的会话数目的方法。预取允许你一次性下载在给定时间内你可能会使用的所有数据。
通过提前加载你的数据,可以减少下载数据所需要的无线连接数。与此同时,你不但节省了电量,也优化了延迟,减少了带宽的占用并减少了下载时间。
预取也可以减少由在显示数据时等待下载完成所造成的延迟。这可以有效提升用户体验。
然而,如果使用的太过度,预取也会引入过量消耗电池和带宽使用的风险,这是因为过度预取可能会取回来一些根本不需要的数据。另外还要确保预取不会造成应用的启动延迟,因为应用可能会等待预取结束。就实际应用而言,这意味着应该要逐步地处理数据,或者说初始化传输的优先级,如应用启动时需要的数据应该优先被下载。
执行预取的程度取决于要被下载的数据规模以及数据被使用的可能性。粗略地来说,基于上述状态机所描述的,对于当前会话中有大约50%几率使用的数据,你可以先预取6秒钟左右(大约1-2Mb),6秒时间的确定指的是在下载无用数据所消耗的时间和不下载这些数据所节省的时间相等时(此时预取已经无法节约时间了)。
通常来说,预取数据时,可以每2-5分钟初始化另一个下载,来下载大约1-5Mb的数据。
遵循上述宗旨,大规模的下载——如视频文件,应该分块以固定时间间隔分别下载(比如每2-5分钟下载一块数据),仅下载极有可能在后几分钟会观看的视频数据。
注意更多下载需要打包下载,这会在下一节详细展开,具体的实现会根据连接的类型和速度而变化,这也会在后续课程中讲到。
下面看一些实际例子:
音乐播放器:
你当然可以一下子把整个专辑预取下来,然而如果用户听完第一首歌后就停止不听了,那么你就消耗了大量的贷款和电池寿命。
一个更好的措施是在当前正在播放的歌曲之外,维持一个装有凌一首歌曲的缓存。对于流媒体音乐,可以使用HTTP流来传输音频,预取操作可以用上述所说的。
新闻阅读器:
许多新闻应用会尝试通过再一种类型的新闻被选中后,仅下载标题的方法来减少带宽,只有在用户选择相应的新闻后再下载文章,只有在用户滑动到显示图片的位置时再加载图片。
通过这种方法,无线连接会一直保持活跃状态,因为用户会不停地在标题间切换,改变新闻类别,并且阅读文章。不仅如此,在能耗状态发生切换时,用于如果切换类别或阅读文章会导致显著的延迟。
一个更好的方法是在启动时先预取合理数量的数据。比如开始可以先取最先的一批新闻标题和缩略图——保证启动的延迟时间较短——之后再陆续获取标题和缩略图,以及相关的正文。
还有一种做法是预取每一个标题,缩略图,正文,甚至新闻完整的图片——可以在后台参照一个预定的时间表执行。但是这个方法会消耗掉大量的带宽和电量去下载那些从来不被使用的内容,所以以这种方式实现的时候一定要格外的注意。
实现时,一种方法是仅在有Wi-Fi连接的时候,还可能是仅在设备在充电的时候执行。具体的细节在这篇文章中有阐述:Modify your Download Patterns Based on the Connectivity Type。
四). 批量传输和连接
每一次你初始化一个连接——不论传输的数据量有多大——你都会导致无线电消耗大约20秒左右的电量(在使用一个标准3G无线网时)。
如果一个应用每20秒ping一次服务器,确认应用处于正在运行并对用户可见的状态,会导致无线电一直消耗电量,即使没有实际的数据传输,也会消耗掉大量的电量。
综上所述,打包你的数据传输是很重要的并创建一个带传输的队列。如果实现正确,你可以有效地在一时间窗口内进行数据传输,让他们同时发生,以尽量保证数据传输在短时间内做完。
这背后的哲学意义便是在传输数据量相同的情况下,要尽量使用最少的会话数目来完成所有数据的传输。
所以说,你应该对于那些可以容忍有一定延迟的数据传输任务执行批量传输,同时要记得当对于时间敏感的传输任务需要做时,它可以具有更高的优先级去做更新或者预取等操作。计划的更新和预取任务应该开始于你的延迟传输队列。
对于一个实际的例子,让我们回到之前预取数据中的例子。
对于一个采用了上述预取策略的新闻应用。新闻阅读器收集并分析信息来理解其用户的阅读模式,并标记出用户最喜欢的文章类型。为了保证该部分文章一直是最新的,它每隔一个小时就刷新一次。为了保留带宽,它并不是下载每篇文章的所有图片,而是仅下载缩略图,并在用户选择了以后再下载完整的图片。
在这个例子中,所有应用收集的分析信息被捆绑在一起,并排成队列等待传输,而不是一收集到就传输。捆绑好的数据的传输时机,可以是当一副完整的图片下载好了,或者每隔一小时的更新发生时(将数据传输的任务合并在一起)。
所有对时间敏感的或者点播类型的数据传输——比如下载一幅完整的图片,需要优先于常规的计划任务。计划更新的执行时机应该是在设置好的计划间隔时间过了之后随着紧急的数据传输一起发生。这种方法通过将计划更新和对时间敏感的下载图片等任务捆绑在一起,来减轻执行计划更新的代价。
五). 减少连接数
通常来说,使用已经存在的网络连接会比重新初始化一个新的网络连接来说更有效率。重复使用连接还能是的网络对于拥塞和相关的网络数据问题的响应可以更加智能。
你应该将请求捆绑到一个GET当中,而不是创建多个连接同时下载数据,或多个连接连续的发送GET请求。
例如,为每一个新闻标题生成单一的请求,并返回单一的请求或响应会比生成多个请求要好。无线需要保持活跃来传输端到端之间的确认接受包,所以在不用网络连接的时候应该将其关闭,而不是让它一直开启并等待超时信息。
也就是说,如果过早地关闭一个连接会阻止它被重用,这就需要额外的消耗来建立新的连接。一个折中的方案是不要立即关闭这些连接,不过还是要在超时时间到之前将其关闭。
六). 使用DDMS的网络流量工具来确定哪里可以改进
Android DDMS (Dalvik Debug Monitor Server)包含有一个详细的网络使用工具,使得其能够在你的应用发出网络连接请求时跟踪网络情况。使用这个工具,你可以监测你的应用是在何时以什么方式传输数据的,并可以根据监测的结果优化你的代码实现。
图3展示了一个每隔15秒钟传输一次少量数据的场景,从中可以发现通过预取或者批量传输的方法可以显著提高应用的传输效率:
图3. 使用DDMS跟踪网络使用情况
通过监听你的数据传输的频率,以及每次传输数据时的数据量,之后你就能找到你的应用可以在什么地方加以改进来提高电池的使用效率。通常而言,你可以找一些可以延迟的波峰,对其进行延迟。或者将一些传输任务提前。
为了更好的表现出传输导致波峰的原因,可以使用Traffic Stats API在一个线程内使用TrafficStats.setThreadStatsTag()方法,人工地标记或者取消标记某一个套接字(使用tagSocket()方法和untagSocket()方法),来标记数据传输的发生。例如:
TrafficStats.setThreadStatsTag(0xF00D); TrafficStats.tagSocket(outputSocket); // Transfer data using socket TrafficStats.untagSocket(outputSocket);
Apache的HttpClient和URLConnection库可以基于当前的getThreadStatsTag()的值自动地为套接字做标记。这些库还能在套接字在活动池中循环后自动地进行标记或取消标记。
TrafficStats.setThreadStatsTag(0xF00D); try { // Make network request using HttpClient.execute() } finally { TrafficStats.clearThreadStatsTag(); }
套接字标记在Android 4.0中开始被支持,但是实时的数据显示仅能在运行Android 4.0.3或更高版本系统的设备上被显示。