天天看點

Android性能優化——優化下載下傳以高效地通路網絡

轉載: http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html;

使用無線電波(wireless radio)進行傳輸資料很可能是我們 app 最耗電的來源之一。為了最小化網絡連接配接對電量的消耗,懂得連接配接模式(connectivity model)會如何影響底層的無線電硬體裝置是至關重要的。

這節課介紹了無線電波狀态機(wireless radio state machine),并解釋了 app 的連接配接模式是如何與狀态機進行互動的。然後會提出建議的方法來最小化我們的資料連接配接,使用預取(prefetching)與捆綁(bundle)的方式進行資料的傳輸,這些操作都是為了最小化電量的消耗。

無線電波狀态機

一個處于完全工作狀态的無線電會大量消耗電量,是以需要學習如何在不同能量狀态下進行過渡,當無線電沒有工作時,節省電量,當需要時嘗試最小化與無線電波供電有關的延遲。

典型的 3G 無線電網絡有三種能量狀态:

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

在低功耗和空閑的狀态下,電量消耗會顯著減少。這裡也會介紹重要的網絡請求延遲。從 low power 能量狀态傳回到 full power 大概需要花費1.5秒,從空閑能量狀态傳回到 full power 狀态需要花費2秒。

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

Android性能優化——優化下載下傳以高效地通路網絡

Figure 1. 典型的 3G 無線電狀态機

在每一台裝置上的無線狀态機,特别是相關的傳輸延遲(“拖尾時間”)和啟動延遲,都會根據無線電波的制式(2G、3G、LTE等)不同而改變,并且由裝置正在所使用的網絡進行定義與配置。

這一課描述了一種典型的 3G 無線電波狀态機,資料來源于 AT&T。無論如何,這些原理和最佳實踐結果是具有通用性的,在其他的無線電波上同樣适用。

這種方法在典型的網頁浏覽操作上是特别有效的,因為它可以阻止使用者在浏覽網頁時的一些不受歡迎的延遲。相對較短的拖尾時間也保證了當一個網頁浏覽會話結束的時候,無線電波可以轉移到相對較低的能量狀态。

不幸的是,這個方法會導緻在現代的智能機系統例如 Android 上的 app 效率低下。因為 Android 上的 app 不僅僅可以在前台運作(重點關注延遲),也可以在背景運作(優先處理耗電量)。(無線電波的狀态改變會影響到本來的設計,有些想在前台運作的可能會因為切換到低能量狀态而影響程式效率。坊間說手機在電量低的狀态下無線電波的強度會增大好幾倍來保證信号,可能與這個有關。)

App 如何影響無線電波狀态機

每次建立一個新的網絡連接配接,無線電波就切換到 full power 狀态。在上面典型的 3G 無線電波狀态機情況下,無線電波會在傳輸資料時保持在 full power 的狀态,加上一個附加的5秒拖尾時間,再之後會經過12秒進入到 low power 能量狀态。是以對于典型的 3G 裝置,每一次資料傳輸的會話都會導緻無線電波消耗大概20秒時間來提取電能。

實際上,這意味着一個每18秒傳輸1秒非捆綁資料(unbundled data)的 app,會一直保持激活狀态(18 = 1秒的傳輸資料 + 5秒過渡時間回到 low power + 12秒過渡時間回到standby)。是以,每分鐘會消耗18秒 high power 的電量,42秒 low power 的電量。

通過比較,同一個 app,每分鐘傳輸持續3秒的捆綁資料(bundle data),會使得無線電波持續在 high power 狀态僅僅8秒,在 low power 狀态僅僅12秒鐘。

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

Android性能優化——優化下載下傳以高效地通路網絡

Figure 2. 無線電波使用捆綁資料 vs 無線電波使用非捆綁資料

預取資料

預取資料是一種減少獨立資料傳輸會話數量的有效方法。預取技術指的是在一定時間内,單次連接配接操作,以最大的下載下傳能力來下載下傳所有使用者可能需要的資料。

通過前面的傳輸資料的技術,減少了大量下載下傳資料所需的無線電波激活時間。這樣不僅節省了電量,也改善了延遲,降低了帶寬,減少了下載下傳時間。

預取技術通過減少應用裡由于在執行一個動作或者檢視資料之前等待下載下傳完成造成的延遲,來提高使用者體驗。

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

根據正在下載下傳的資料大小與可能被用到的資料量來決定預取的頻率。作一個粗略的估計,根據上面介紹的狀态機,對于有50%的機會被目前的使用者會話用到的資料,我們可以預取大約6秒(大約1-2Mb),這大概使得潛在可能要用的資料量與可能已經下載下傳好的資料量相一緻。

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

根據這個原理,大資料的下載下傳,比如視訊檔案,應該每隔2-5分鐘開始另一段下載下傳,這樣能有效的預取到下面幾分鐘内的資料進行預覽。

值得注意的是,更進一步的下載下傳應該是是捆綁的(bundled),下一小節将會講到,批量處理傳送和連接配接,而且上面那些大概的資料與時間可能會根據網絡連接配接的類型與速度有所變化,這将在根據網絡連接配接類型來調整下載下傳模式講到。

讓我們來看一些例子:

一個音樂播放器

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

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

一個新聞閱讀器

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

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

一個比較好的方法是在啟動的時候預取一個合理數量的資料,比如在啟動的時候預取第一條新聞的标題與縮略圖資訊,確定較短的啟動時間。之後繼續擷取剩餘新聞的标題和縮略圖資訊。同時擷取至少在主要标題清單中可用的每篇文章的文本。

另一個方法是預取所有的标題,縮略資訊,文章文字,甚至是所有文章的圖檔——根據既設的背景程式進行逐一擷取。這樣做的風險是花費了大量的帶寬與電量去下載下傳一些不會閱讀到的内容,是以應該謹慎使用這種方法。

其中的一個解決方案是,僅當在連接配接至Wi-Fi或者裝置正在充電時,排程到 Full power 狀态進行下載下傳。關于這個細節的實作,我們将在後面的根據網絡連接配接類型來調整下載下傳模式課程中介紹。

批量處理傳送和連接配接

每次發起一個連接配接——不論相關傳送資料的大小——當使用典型的 3G 無線網絡時,可能會導緻無線電波消耗大約20秒的電量。

一個 app 每20秒 ping 一次伺服器,僅僅是為了确認 app 正在運作和對使用者可見,那麼無線電波會無限期地處于開啟狀态,導緻即使在沒有實際資料傳輸的情況下,仍會消耗大量電量。

是以,對傳送的資料進行捆綁操作和建立一個等待傳輸隊列就顯得非常重要。操作正确的話,可以使得大量的資料集中進行發送,這樣使得無線電波的激活時間盡可能的少,同時減少大部分電量的花費。

這樣做的潛在好處是盡可能在每次傳輸資料的會話中盡可能多的傳輸資料而且減少了會話的次數。

那就意味着我們應該通過隊列延遲容忍傳送來批量處理我們的傳輸資料,和搶占排程更新和預取,使得當要求時間敏感傳輸時,資料會被全部執行。同樣地,我們的計劃更新和定期的預取應該開啟等待傳輸隊列的執行工作。

預取資料部分有一個實際的例子。

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

在這個例子中,所有在 app 中收集到的分析資訊應該捆綁在一起并放入下載下傳隊列,而不是一收集到資訊就傳輸。當下載下傳完一張全尺寸的圖檔或者執行每小時一次更新時,應該傳輸捆綁好的資料。

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

減少連接配接

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

當可以捆綁所有請求在一個 GET 裡面的時候,不要同時建立多個網絡連接配接或者把多個 GET 請求進行串聯。

例如,可以一起請求所有文章的情況下,不要根據多個新聞會話進行多次請求。為傳輸與服務端和用戶端 timeout 相關的終止 / 終止确認資料包,無線電波會保持激活狀态,是以如果不需要使用連接配接時,請立即關閉,而不是等待他們 timeout。

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

使用 DDMS Network Traffic Tool 來确定問題的區域

Android DDMS (Dalvik Debug Monitor Server) 包含了一個檢視網絡使用詳情的欄目來允許跟蹤 app 的網絡請求。使用這個工具,可以監測 app 是在何時,如何傳輸資料的,進而進行代碼的優化。

Figure 3 顯示了傳輸少量資料的網絡模型,可以看到每次差不多相隔15秒,這意味着可以通過預取技術或者批量上傳來大幅提高效率。

Android性能優化——優化下載下傳以高效地通路網絡

Figure 3. 使用 DDMS 檢測網絡使用情況

通過監測資料傳輸的頻率與每次傳輸的資料量,可以檢視出哪些位置應該進行優化。通常的,我們會尋找類似短穗狀的地方,這些位置可以延遲,或者應該導緻一個後來的傳輸被搶占。

為了更好的檢測出問題所在,Traffic Status API 允許我們使用 TrafficStats.setThreadStatsTag() 方法标記資料傳輸發生在某個Thread裡面,然後可以手動地使用 tagSocket() 進行标記或者使用 untagSocket()` 來取消标記,例如:

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

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

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

給 Socket 加上标簽(Socket tagging)是在 Android 4.0 上才被支援的, 但是實際情況是僅僅會在運作Android 4.0.3 或者更高版本的裝置上才會顯示。