1. 程式人生 > >Android效能優化二 電量優化、網路優化、物件池、bitmap解位元速率

Android效能優化二 電量優化、網路優化、物件池、bitmap解位元速率

大綱

電量優化、網路優化、Android Wear上如何做優化、使用物件池來提高效率、LRU Cache、Bitmap的縮放、快取、重用、PNG壓縮、自定義View的效能、提升設定alpha之後View的渲染效能,以及Lint、StictMode等工具的使用技巧

對於手機程式,網路操作相對來說是比較耗電的行為。優化網路操作能夠顯著節約電量的消耗。

當程式想要執行某個網路請求之前,需要先喚醒裝置,然後傳送資料請求,之後等待返回資料,最後才慢慢進入休眠狀態。這個流程如下圖所示:

從圖示中可以看到,啟用瞬間,傳送資料的瞬間,接收資料的瞬間都有很大的電量消耗,所以,我們應該從如何傳遞網路資料以及何時發起網路請求這兩個方面來著手優化。


1.1) 何時發起網路請求

首先我們需要區分哪些網路請求是需要及時返回結果的,哪些是可以延遲執行的。例如,使用者主動下拉重新整理列表,這種行為需要立即觸發網路請求,並等待資料返回。但是對於上傳使用者操作的資料,同步程式設定等等行為則屬於可以延遲的行為。我們可以通過Battery Historian這個工具來檢視關於移動蜂窩模組的電量消耗。在Mobile Radio那一行會顯示蜂窩模組的電量消耗情況,紅色的部分代表模組正在工作,中間的間隔部分代表模組正在休眠狀態,如果看到有一段區間,紅色與間隔頻繁的出現,那就說明這裡有可以優化的行為。如下圖所示:


對於上面可以優化的部分,我們可以有針對性的把請求行為捆綁起來,延遲到某個時刻統一發起請求。如下圖所示:


經過上面的優化之後,我們再回頭使用Battery Historian匯出電量消耗圖,可以看到喚醒狀態與休眠狀態是連續大塊間隔的,這樣的話,總體電量的消耗就會變得更少。


當然,我們甚至可以把請求的任務延遲到手機網路切換到WiFi,手機處於充電狀態下再執行。在前面的描述過程中,我們會遇到的一個難題是如何把網路請求延遲,並批量進行執行。還好,Android提供了JobScheduler來幫助我們達成這個目標。


(5)) Object Pools

在程式裡面經常會遇到的一個問題是短時間內建立大量的物件,導致記憶體緊張,從而觸發GC導致效能問題。對於這個問題,我們可以使用物件池技術來解決它。通常物件池中的物件可能是bitmaps,views,paints等等。關於物件池的操作原理,不展開述說了,請看下面的圖示:


使用物件池技術有很多好處,它可以避免記憶體抖動,提升效能,但是在使用的時候有一些內容是需要特別注意的。通常情況下,初始化的物件池裡面都是空白的,當使用某個物件的時候先去物件池查詢是否存在,如果不存在則建立這個物件然後加入物件池,但是我們也可以在程式剛啟動的時候就事先為物件池填充一些即將要使用到的資料,這樣可以在需要使用到這些物件的時候提供更快的首次載入速度,這種行為就叫做預分配。使用物件池也有不好的一面,程式設計師需要手動管理這些物件的分配與釋放,所以我們需要慎重地使用這項技術,避免發生物件的記憶體洩漏。為了確保所有的物件能夠正確被釋放,我們需要保證加入物件池的物件和其他外部物件沒有互相引用的關係。


7) The Magic of LRU Cache

這小節我們要討論的是快取演算法,在Android上面最常用的一個快取演算法是LRU(Least Recently Use),關於LRU演算法,不展開述說,用下面一張圖演示下含義:


LRU Cache的基礎構建用法如下:


為了給LRU Cache設定一個比較合理的快取大小值,我們通常是用下面的方法來做界定的:


使用LRU Cache時為了能夠讓Cache知道每個加入的Item的具體大小,我們需要Override下面的方法:


使用LRU Cache能夠顯著提升應用的效能,可是也需要注意LRU Cache中被淘汰物件的回收,否者會引起嚴重的記憶體洩露。

8) Using LINT for Performance Tips

Lint是Android提供的一個靜態掃描應用原始碼並找出其中的潛在問題的一個強大的工具。


例如,如果我們在onDraw方法裡面執行了new物件的操作,Lint就會提示我們這裡有效能問題,並提出對應的建議方案。Lint已經整合到Android Studio中了,我們可以手動去觸發這個工具,點選工具欄的Analysis -> Inspect Code,觸發之後,Lint會開始工作,並把結果輸出到底部的工具欄,我們可以逐個檢視原因並根據指示做相應的優化修改。

Lint的功能非常強大,他能夠掃描各種問題。當然我們可以通過Android Studio設定找到Lint,對Lint做一些定製化掃描的設定,可以選擇忽略掉那些不想Lint去掃描的選項,我們還可以針對部分掃描內容修改它的提示優先順序。

建議把與記憶體有關的選項中的嚴重程度標記為紅色的Error,對於Layout的效能問題標記為黃色Warning。

10) Avoiding Allocations in onDraw()

我們都知道應該避免在onDraw()方法裡面執行導致記憶體分配的操作,下面講解下為何需要這樣做。

首先onDraw()方法是執行在UI執行緒的,在UI執行緒儘量避免做任何可能影響到效能的操作。雖然分配記憶體的操作並不需要花費太多系統資源,但是這並不意味著是免費無代價的。裝置有一定的重新整理頻率,導致View的onDraw方法會被頻繁的呼叫,如果onDraw方法效率低下,在頻繁重新整理累積的效應下,效率低的問題會被擴大,然後會對效能有嚴重的影響。


如果在onDraw裡面執行記憶體分配的操作,會容易導致記憶體抖動,GC頻繁被觸發,雖然GC後來被改進為執行在另外一個後臺執行緒(GC操作在2.3以前是同步的,之後是併發),可是頻繁的GC的操作還是會影響到CPU,影響到電量的消耗。

那麼簡單解決頻繁分配記憶體的方法就是把分配操作移動到onDraw()方法外面,通常情況下,我們會把onDraw()裡面new Paint的操作移動到外面,如下面所示:


11) Tool: Strict Mode

UI執行緒被阻塞超過5秒,就會出現ANR,這太糟糕了。防止程式出現ANR是很重要的事情,那麼如何找出程式裡面潛在的坑,預防ANR呢?很多大部分情況下執行很快的方法,但是他們有可能存在巨大的隱患,這些隱患的爆發就很容易導致ANR。

Android提供了一個叫做Strict Mode的工具,我們可以通過手機設定裡面的開發者選項,開啟Strict Mode選項,如果程式存在潛在的隱患,螢幕就會閃現紅色。我們也可以通過StrictMode API在程式碼層面做細化的跟蹤,可以設定StrictMode監聽那些潛在問題,出現問題時如何提醒開發者,可以對螢幕閃紅色,也可以輸出錯誤日誌。下面是官方的程式碼示例:

  1. publicvoid onCreate() {  
  2.      if (DEVELOPER_MODE) {  
  3.          StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()  
  4.                  .detectDiskReads()  
  5.                  .detectDiskWrites()  
  6.                  .detectNetwork()   // or .detectAll() for all detectable problems
  7.                  .penaltyLog()  
  8.                  .build());  
  9.          StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()  
  10.                  .detectLeakedSqlLiteObjects()  
  11.                  .detectLeakedClosableObjects()  
  12.                  .penaltyLog()  
  13.                  .penaltyDeath()  
  14.                  .build());  
  15.      }  
  16.      super.onCreate();  
  17. }  

14) Smaller Pixel Formats

常見的png,jpeg,webp等格式的圖片在設定到UI上之前需要經過解碼的過程,而解壓時可以選擇不同的解位元速率,不同的解位元速率對記憶體的佔用是有很大差別的。在不影響到畫質的前提下儘量減少記憶體的佔用,這能夠顯著提升應用程式的效能。

Android的Heap空間是不會自動做相容壓縮的,意思就是如果Heap空間中的圖片被收回之後,這塊區域並不會和其他已經回收過的區域做重新排序合併處理,那麼當一個更大的圖片需要放到heap之前,很可能找不到那麼大的連續空閒區域,那麼就會觸發GC,使得heap騰出一塊足以放下這張圖片的空閒區域,如果無法騰出,就會發生OOM。如下圖所示:


所以為了避免載入一張超大的圖片,需要儘量減少這張圖片所佔用的記憶體大小,Android為圖片提供了4種解碼格式,他們分別佔用的記憶體大小如下圖所示:


隨著解碼佔用記憶體大小的降低,清晰度也會有損失。我們需要針對不同的應用場景做不同的處理,大圖和小圖可以採用不同的解位元速率。在Android裡面可以通過下面的程式碼來設定解位元速率:

18) The Performance Lifecycle

大多數開發者在沒有發現嚴重效能問題之前是不會特別花精力去關注效能優化的,通常大家關注的都是功能是否實現。當效能問題真的出現的時候,請不要慌亂。我們通常採用下面三個步驟來解決效能問題。

  • Gather:收集資料

我們可以通過Android SDK裡面提供的諸多工具來收集CPU、GPU、記憶體、電量等效能資料。

  • Insight:分析資料

通過上面的步驟,我們獲取到了大量的資料,下一步就是分析這些資料。工具幫我們生成了很多可讀性強的表格,我們需要事先了解如何查看錶格的資料,每一項代表的含義,這樣才能夠快速定位問題。如果分析資料之後還是沒有找到問題,那麼就只能不停的重新收集資料,再進行分析,如此迴圈。

  • Action:解決問題

定位到問題之後,我們需要採取行動來解決問題。解決問題之前一定要先有個計劃,評估這個解決方案是否可行,是否能夠及時的解決問題。