Android優(yōu)化下載以高效地訪問網(wǎng)絡

2018-08-02 17:42 更新

編寫:kesenhoo - 原文:http://developer.android.com/training/efficient-downloads/efficient-network-access.html

使用無線電波(wireless radio)進行傳輸數(shù)據(jù)很可能是我們 app 最耗電的來源之一。為了最小化網(wǎng)絡連接對電量的消耗,懂得連接模式(connectivity model)會如何影響底層的無線電硬件設備是至關(guān)重要的。

這節(jié)課介紹了無線電波狀態(tài)機(wireless radio state machine),并解釋了 app 的連接模式是如何與狀態(tài)機進行交互的。然后會提出建議的方法來最小化我們的數(shù)據(jù)連接,使用預?。╬refetching)與捆綁(bundle)的方式進行數(shù)據(jù)的傳輸,這些操作都是為了最小化電量的消耗。

無線電波狀態(tài)機

一個處于完全工作狀態(tài)的無線電會大量消耗電量,因此需要學習如何在不同能量狀態(tài)下進行過渡,當無線電沒有工作時,節(jié)省電量,當需要時嘗試最小化與無線電波供電有關(guān)的延遲。

典型的 3G 無線電網(wǎng)絡有三種能量狀態(tài):

  1. Full power:當無線連接被激活的時候,允許設備以最大的傳輸速率進行操作。
  2. Low power:一種中間狀態(tài),對電量的消耗差不多是 Full power 狀態(tài)下的50%。
  3. Standby:最小的能量狀態(tài),沒有被激活或者需求的網(wǎng)絡連接。

在低功耗和空閑的狀態(tài)下,電量消耗會顯著減少。這里也會介紹重要的網(wǎng)絡請求延遲。從 low power 能量狀態(tài)返回到 full power 大概需要花費1.5秒,從空閑能量狀態(tài)返回到 full power 狀態(tài)需要花費2秒。

為了最小化延遲,狀態(tài)機使用了一種后滯過渡到更低能量狀態(tài)的機制。下圖是一個典型的 3G 無線電波狀態(tài)機的圖示(AT&T電信的一種制式)。

mobile_radio_state_machine.png

Figure 1. 典型的 3G 無線電狀態(tài)機

在每一臺設備上的無線狀態(tài)機,特別是相關(guān)的傳輸延遲(“拖尾時間”)和啟動延遲,都會根據(jù)無線電波的制式(2G、3G、LTE等)不同而改變,并且由設備正在所使用的網(wǎng)絡進行定義與配置。

這一課描述了一種典型的 3G 無線電波狀態(tài)機,數(shù)據(jù)來源于 AT&T。無論如何,這些原理和最佳實踐結(jié)果是具有通用性的,在其他的無線電波上同樣適用。

這種方法在典型的網(wǎng)頁瀏覽操作上是特別有效的,因為它可以阻止用戶在瀏覽網(wǎng)頁時的一些不受歡迎的延遲。相對較短的拖尾時間也保證了當一個網(wǎng)頁瀏覽會話結(jié)束的時候,無線電波可以轉(zhuǎn)移到相對較低的能量狀態(tài)。

不幸的是,這個方法會導致在現(xiàn)代的智能機系統(tǒng)例如 Android 上的 app 效率低下。因為 Android 上的 app 不僅僅可以在前臺運行(重點關(guān)注延遲),也可以在后臺運行(優(yōu)先處理耗電量)。(無線電波的狀態(tài)改變會影響到本來的設計,有些想在前臺運行的可能會因為切換到低能量狀態(tài)而影響程序效率。坊間說手機在電量低的狀態(tài)下無線電波的強度會增大好幾倍來保證信號,可能與這個有關(guān)。)

App 如何影響無線電波狀態(tài)機

每次創(chuàng)建一個新的網(wǎng)絡連接,無線電波就切換到 full power 狀態(tài)。在上面典型的 3G 無線電波狀態(tài)機情況下,無線電波會在傳輸數(shù)據(jù)時保持在 full power 的狀態(tài)(加上一個附加的5秒拖尾時間),再之后會經(jīng)過12秒的 low power 能量狀態(tài)。因此對于典型的 3G 設備,每一次數(shù)據(jù)傳輸?shù)臅挾紩е聼o線電波消耗大概20秒時間來提取電能。

實際上,這意味著一個每18秒傳輸1秒非捆綁數(shù)據(jù)(unbundled data)的 app,會一直保持激活狀態(tài)(18 = 1秒的傳輸數(shù)據(jù) + 5秒過渡時間回到 low power + 12秒過渡時間回到standby)。因此,每分鐘會消耗18秒 high power 的電量,42秒 low power 的電量。

通過比較,同一個 app,每分鐘傳輸持續(xù)3秒的捆綁數(shù)據(jù)(bundle data),會使得無線電波持續(xù)在 high power 狀態(tài)僅僅8秒,在 low power 狀態(tài)僅僅12秒鐘。

上面第二種傳輸捆綁數(shù)據(jù)(bundle data)的例子,可以看到減少了大量的電量消耗。圖示如下:

graphs.png

Figure 2. 無線電波使用捆綁數(shù)據(jù) vs 無線電波使用非捆綁數(shù)據(jù)

預取數(shù)據(jù)

預取數(shù)據(jù)是一種減少獨立數(shù)據(jù)傳輸會話數(shù)量的有效方法。預取技術(shù)指的是在一定時間內(nèi),單次連接操作,以最大的下載能力來下載所有用戶可能需要的數(shù)據(jù)。

通過前面的傳輸數(shù)據(jù)的技術(shù),減少了大量下載數(shù)據(jù)所需的無線電波激活時間。這樣不僅節(jié)省了電量,也改善了延遲,降低了帶寬,減少了下載時間。

預取技術(shù)通過減少應用里由于在執(zhí)行一個動作或者查看數(shù)據(jù)之前等待下載完成造成的延遲,來提高用戶體驗。

然而,過于頻繁地使用預取技術(shù),不僅僅會導致電量消耗快速增長,還有可能預取到一些并不需要的數(shù)據(jù),導致增加帶寬的使用和下載配額。另外,需要確保預取不會因為 app 等待預取全部完成而延遲應用的啟動。從實踐的角度,那意味著需要逐步處理數(shù)據(jù),或者按照優(yōu)先級順序開始進行持續(xù)的數(shù)據(jù)傳遞,這樣會首先下載和處理應用啟動時需要的數(shù)據(jù)。

根據(jù)正在下載的數(shù)據(jù)大小與可能被用到的數(shù)據(jù)量來決定預取的頻率。作一個粗略的估計,根據(jù)上面介紹的狀態(tài)機,對于有50%的機會被當前的用戶會話用到的數(shù)據(jù),我們可以預取大約6秒(大約1-2Mb),這大概使得潛在可能要用的數(shù)據(jù)量與可能已經(jīng)下載好的數(shù)據(jù)量相一致。

通常來說,預取1-5Mb會比較好,這種情況下,我們僅僅只需要每隔2-5分鐘開始另一段下載。

根據(jù)這個原理,大數(shù)據(jù)的下載,比如視頻文件,應該每隔2-5分鐘開始另一段下載,這樣能有效的預取到下面幾分鐘內(nèi)的數(shù)據(jù)進行預覽。

值得注意的是,更進一步的下載應該是是捆綁的(bundled),下一小節(jié)將會講到,批量處理傳送和連接,而且上面那些大概的數(shù)據(jù)與時間可能會根據(jù)網(wǎng)絡連接的類型與速度有所變化,這將在根據(jù)網(wǎng)絡連接類型來調(diào)整下載模式講到。

讓我們來看一些例子:

一個音樂播放器

我們可以選擇預取整個專輯,然而這樣在第一首歌曲之后用戶會停止聽歌,那么就浪費了大量的帶寬和電量。

一個比較好的方法是維護正在播放的那首歌曲的緩沖區(qū)。對于流媒體音樂,不應該去維護一段連續(xù)的數(shù)據(jù)流,因為這樣會使得無線電波一直保持激活狀態(tài),而應該考慮用 HTTP 流直播來集中傳輸音頻流,就像上面描述的預取技術(shù)一樣(下載好2Mb,然后開始一次取出,再去下載下面的2Mb)。

一個新聞閱讀器

許多新聞 app 嘗試通過只下載新聞標題來減少帶寬,完整的文章僅在用戶想要讀取的時候再去讀取,而且文章也會因為太長而剛開始只顯示部分信息,等用戶下滑時再去讀取完整信息。

使用這個方法,無線電波僅僅會在用戶點擊更多信息的時候才會被激活。但是,在切換文章分類預閱讀文章的時候仍然會造成大量潛在的消耗。

一個比較好的方法是在啟動的時候預取一個合理數(shù)量的數(shù)據(jù),比如在啟動的時候預取第一條新聞的標題與縮略圖信息,確保較短的啟動時間。之后繼續(xù)獲取剩余新聞的標題和縮略圖信息。同時獲取至少在主要標題列表中可用的每篇文章的文本。

另一個方法是預取所有的標題,縮略信息,文章文字,甚至是所有文章的圖片——根據(jù)既設的后臺程序進行逐一獲取。這樣做的風險是花費了大量的帶寬與電量去下載一些不會閱讀到的內(nèi)容,因此應該謹慎使用這種方法。

其中的一個解決方案是,僅當在連接至Wi-Fi或者設備正在充電時,調(diào)度到 Full power 狀態(tài)進行下載。關(guān)于這個細節(jié)的實現(xiàn),我們將在后面的根據(jù)網(wǎng)絡連接類型來調(diào)整下載模式課程中介紹。

批量處理傳送和連接

每次發(fā)起一個連接——不論相關(guān)傳送數(shù)據(jù)的大小——當使用典型的 3G 無線網(wǎng)絡時,可能會導致無線電波消耗大約20秒的電量。

一個 app 每20秒 ping 一次服務器,僅僅是為了確認 app 正在運行和對用戶可見,那么無線電波會無限期地處于開啟狀態(tài),導致即使在沒有實際數(shù)據(jù)傳輸?shù)那闆r下,仍會消耗大量電量。

因此,對傳送的數(shù)據(jù)進行捆綁操作和創(chuàng)建一個等待傳輸隊列就顯得非常重要。操作正確的話,可以使得大量的數(shù)據(jù)集中進行發(fā)送,這樣使得無線電波的激活時間盡可能的少,同時減少大部分電量的花費。

這樣做的潛在好處是盡可能在每次傳輸數(shù)據(jù)的會話中盡可能多的傳輸數(shù)據(jù)而且減少了會話的次數(shù)。

那就意味著我們應該通過隊列延遲容忍傳送來批量處理我們的傳輸數(shù)據(jù),和搶占調(diào)度更新和預取,使得當要求時間敏感傳輸時,數(shù)據(jù)會被全部執(zhí)行。同樣地,我們的計劃更新和定期的預取應該開啟等待傳輸隊列的執(zhí)行工作。

預取數(shù)據(jù)部分有一個實際的例子。

以上述使用定期預取的新聞應用為例。新聞閱讀器收集分析用戶的信息來了解用戶的閱讀模式,并按照新聞報道的受歡迎程度對新聞進行排序。為了保證新聞最新,應用每個小時會檢查更新一次。為了節(jié)省帶寬,預取縮略圖信息和當用戶選擇某個新聞時下載全部圖片,而不去下載每篇文章的所有圖片。

在這個例子中,所有在 app 中收集到的分析信息應該捆綁在一起并放入下載隊列,而不是一收集到信息就傳輸。當下載完一張全尺寸的圖片或者執(zhí)行每小時一次更新時,應該傳輸捆綁好的數(shù)據(jù)。

任何時間敏感或者按需的傳輸——例如下載全尺寸圖片——應該搶占定期更新。計劃好的更新應該與按需傳送在同一時間執(zhí)行。這個方法減小了執(zhí)行一個定期更新的開銷,該定期更新通過下載必要的時間敏感圖片的背負式傳輸實現(xiàn)。

減少連接

通常來說,重用已經(jīng)存在的網(wǎng)絡連接比起重新建立一個新的連接更有效率。重用網(wǎng)絡連接同樣可以使得在擁擠不堪的網(wǎng)絡環(huán)境中進行更加智能地作出反應。

當可以捆綁所有請求在一個 GET 里面的時候,不要同時創(chuàng)建多個網(wǎng)絡連接或者把多個 GET 請求進行串聯(lián)。

例如,可以一起請求所有文章的情況下,不要根據(jù)多個新聞會話進行多次請求。為傳輸與服務端和客戶端 timeout 相關(guān)的終止 / 終止確認數(shù)據(jù)包,無線電波會保持激活狀態(tài),所以如果不需要使用連接時,請立即關(guān)閉,而不是等待他們 timeout。

之前說道,如果過早對一個連接執(zhí)行關(guān)閉操作,會導致需要額外的開銷來建立一個新的連接。一個有用的妥協(xié)是不要立即關(guān)閉連接,而是在固定期間的 timeout 之前關(guān)閉(即稍微晚點卻又不至于到 timeout)。

使用 DDMS Network Traffic Tool 來確定問題的區(qū)域

Android DDMS (Dalvik Debug Monitor Server) 包含了一個查看網(wǎng)絡使用詳情的欄目來允許跟蹤 app 的網(wǎng)絡請求。使用這個工具,可以監(jiān)測 app 是在何時,如何傳輸數(shù)據(jù)的,從而進行代碼的優(yōu)化。

Figure 3 顯示了傳輸少量數(shù)據(jù)的網(wǎng)絡模型,可以看到每次差不多相隔15秒,這意味著可以通過預取技術(shù)或者批量上傳來大幅提高效率。

DDMS.png

Figure 3. 使用 DDMS 檢測網(wǎng)絡使用情況

通過監(jiān)測數(shù)據(jù)傳輸?shù)念l率與每次傳輸?shù)臄?shù)據(jù)量,可以查看出哪些位置應該進行優(yōu)化。通常的,我們會尋找類似短穗狀的地方,這些位置可以延遲,或者應該導致一個后來的傳輸被搶占。

為了更好的檢測出問題所在,Traffic Status API 允許我們使用 TrafficStats.setThreadStatsTag() 方法標記數(shù)據(jù)傳輸發(fā)生在某個Thread里面,然后可以手動地使用 tagSocket() 進行標記或者使用 untagSocket()` 來取消標記,例如:

TrafficStats.setThreadStatsTag(0xF00D);
TrafficStats.tagSocket(outputSocket);
// Transfer data using socket
TrafficStats.untagSocket(outputSocket);

Apache 的 HttpClient 與 URLConnection 庫可以根據(jù)當前的 getThreadStatusTag() 值自動給 sockets 加上標記。那些庫在通過 keep-alive pools 循環(huán)的時候也會為 sockets 加上或者取消標簽。

TrafficStats.setThreadStatsTag(0xF00D);
try {
  // Make network request using HttpClient.execute()
} finally {
  TrafficStats.clearThreadStatsTag();
}

給 Socket 加上標簽(Socket tagging)是在 Android 4.0 上才被支持的, 但是實際情況是僅僅會在運行Android 4.0.3 或者更高版本的設備上才會顯示。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號