Android效能優化典範(2)

desaco發表於2016-01-16

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

    可以通過Battery Historian這個工具來檢視關於移動蜂窩模組的電量消耗。在Mobile Radio那一行會顯示蜂窩模組的電量消耗情況,紅色的部分代表模組正在工作,中間的間隔部分代表模組正在休眠狀態,如果看到有一段區間,紅色與間隔頻繁的出現,那就說明這裡有可以優化的行為。

    如何傳遞網路資料:涉及到Prefetch(預取)與Compressed(壓縮)這兩個技術。想要知道我們的應用程式中網路請求發生的時間,每次請求的資料量等等資訊,可以通過Android Studio中的Networking Traffic Tool來檢視詳細的資料。

    Android Wear儘量減少重新整理請求,例如我們可以在不需要某些資料的時候儘快登出監聽,減小重新整理頻率,對Sensor的資料做批量處理等:

  • 首先我們需要儘量使用Android平臺提供的既有運動資料,而不是自己去實現監聽採集資料,因為大多數Android Watch自身記錄Sensor資料的行為是有經過做電量優化的。
  • 其次在Activity不需要監聽某些Sensor資料的時候需要儘快釋放監聽註冊。
  • 還有我們需要儘量控制更新的頻率,僅僅在需要重新整理顯示資料的時候才觸發獲取最新資料的操作。
  • 另外我們可以針對Sensor的資料做批量處理,待資料累積一定次數或者某個程度的時候才更新到UI上。
  • 最後當Watch與Phone連線起來的時候,可以把某些複雜操作的事情交給Phone來執行,Watch只需要等待返回的結果。
Android Material Design風格的應用採用了大量的動畫來進行UI切換,優化動畫的效能不僅能夠提升使用者體驗還可以減少電量的消耗。

  在Android裡面一個相對操作比較繁重的事情是對Bitmap進行旋轉,縮放,裁剪等等。例如在一個圓形的鐘表圖上,我們把時鐘的指標摳出來當做單獨的圖片進行旋轉會比旋轉一張完整的圓形圖的所形成的幀率要高56%。

  另外儘量減少每次重繪的元素可以極大的提升效能,假如某個鐘錶介面上有很多需要顯示的複雜元件,我們可以把這些元件做拆分處理,例如把背景圖片單獨拎出來設定為一個獨立的View,通過setLayerType()方法使得這個View強制用Hardware來進行渲染。至於介面上哪些元素需要做拆分,他們各自的更新頻率是多少,需要有針對性的單獨討論。

 > 如何使用Systrace等工具來檢視某些View的渲染效能?

 > 對於大多數應用中的動畫,我們會使用PropertyAnimation或者ViewAnimation來操作實現,Android系統會自動對這些Animation做一定的優化處理,在Android上面學習到的大多數效能優化的知識同樣也適用於Android Wear。

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

  快取演算法,在Android上面最常用的一個快取演算法是LRU(Least Recently Use)。

  如果我們在onDraw方法裡面執行了new物件的操作,Lint就會提示我們這裡有效能問題,並提出對應的建議方案。Lint的功能非常強大,他能夠掃描各種問題。當然我們可以通過Android Studio設定找到Lint,對Lint做一些定製化掃描的設定,可以選擇忽略掉那些不想Lint去掃描的選項,我們還可以針對部分掃描內容修改它的提示優先順序。

通過StrictMode API在程式碼層面做細化的跟蹤,可以設定StrictMode監聽那些潛在問題,出現問題時如何提醒開發者,可以對螢幕閃紅色,也可以輸出錯誤日誌。

  1. public void 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. }  

紹如何優化自定義View的效能。

通常來說,針對自定義View,我們可能犯下面三個錯誤:

  • Useless calls to onDraw():我們知道呼叫View.invalidate()會觸發View的重繪,有兩個原則需要遵守,第1個是僅僅在View的內容發生改變的時候才去觸發invalidate方法,第2個是儘量使用ClipRect等方法來提高繪製的效能。
  • Useless pixels:減少繪製時不必要的繪製元素,對於那些不可見的元素,我們需要儘量避免重繪。
  • Wasted CPU cycles:對於不在螢幕上的元素,可以使用Canvas.quickReject把他們給剔除,避免浪費CPU資源。另外儘量使用GPU來進行UI的渲染,這樣能夠極大的提高程式的整體表現效能。

最後請時刻牢記,儘量提高View的繪製效能,這樣才能保證介面的重新整理幀率儘量的高。

 >  移動蜂窩模組的電量消耗模型。為了避免我們的應用程式過多的頻繁消耗電量,我們需要學習如何把後臺任務打包批量,並選擇一個合適的時機進行觸發執行。

  把部分應用的任務延遲處理,等到一定時機,這些任務一併進行處理。

》執行延遲任務,通常有下面三種方式:

  • AlarmManager:使用AlarmManager設定定時任務,可以選擇精確的間隔時間,也可以選擇非精確時間作為引數。除非程式有很強烈的需要使用精確的定時喚醒,否者一定要避免使用他,我們應該儘量使用非精確的方式。

  • SyncAdapter:我們可以使用SyncAdapter為應用新增設定賬戶,這樣在手機設定的賬戶列表裡面可以找到我們的應用。這種方式功能更多,但是實現起來比較複雜(Google官方培訓課程

  • JobSchedulor:這是最簡單高效的方法,我們可以設定任務延遲的間隔,執行條件,還可以增加重試機制。
>Smaller Pixel Formats

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

Android的Heap空間是不會自動做相容壓縮的,意思就是如果Heap空間中的圖片被收回之後,這塊區域並不會和其他已經回收過的區域做重新排序合併處理,那麼當一個更大的圖片需要放到heap之前,很可能找不到那麼大的連續空閒區域,那麼就會觸發GC,使得heap騰出一塊足以放下這張圖片的空閒區域,如果無法騰出,就會發生OOM。所以為了避免載入一張超大的圖片,需要儘量減少這張圖片所佔用的記憶體大小,Android為圖片提供了4種解碼格式.

儘量減少PNG圖片的大小是Android裡面很重要的一條規範。相比起JPEG,PNG能夠提供更加清晰無損的圖片,但是PNG格式的圖片會更大,佔用更多的磁碟空間。到底是使用PNG還是JPEG,需要設計師仔細衡量,對於那些使用JPEG就可以達到視覺效果的,可以考慮採用JPEG即可。

介紹一種新的圖片格式:Webp,它是由Google推出的一種既保留png格式的優點,又能夠減少圖片大小的一種新型圖片格式。

對bitmap做縮放,這也是Android裡面最遇到的問題。對bitmap做縮放的意義很明顯,提示顯示效能,避免分配不必要的記憶體。Android提供了現成的bitmap縮放的API,叫做createScaledBitmap(),使用這個方法可以獲取到一張經過縮放的圖片。inSampleSize能夠等比的縮放顯示圖片,同時還避免了需要先把原圖載入進記憶體的缺點。還可以使用inScaled,inDensity,inTargetDensity的屬性來對解碼圖片做處理.還有一個經常使用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖片,可以事先獲取到圖片的大小而不至於佔用什麼記憶體。

bitmap會佔用大量的記憶體空間,這節會講解什麼是inBitmap屬性,如何利用這個屬性來提升bitmap的迴圈效率。前面我們介紹過使用物件池的技術來解決物件頻繁建立再回收的效率問題,使用這種方法,bitmap佔用的記憶體空間會差不多是恆定的數值,每次新建立出來的bitmap都會需要佔用一塊單獨的記憶體區域. 使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所佔據的pixel data記憶體區域,而不是去問記憶體重新申請一塊區域來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用螢幕所能夠顯示的圖片數量的記憶體大小。下

     使用inBitmap需要注意幾個限制條件:

  • 在SDK 11 -> 18之間,重用的bitmap大小必須是一致的,例如給inBitmap賦值的圖片大小為100-100,那麼新申請的bitmap必須也為100-100才能夠被重用。從SDK 19開始,新申請的bitmap大小必須小於或者等於已經賦值過的bitmap大小。
  • 新申請的bitmap與舊的bitmap必須有相同的解碼格式,例如大家都是8888的,如果前面的bitmap是8888,那麼就不能支援4444與565格式的bitmap了。
Google介紹了一個開源的載入bitmap的庫:Glide,這裡麵包含了各種對bitmap的優化技巧。

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

  • Gather:收集資料

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

  • Insight:分析資料

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

  • Action:解決問題

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

> 圍繞Android生態系統,不僅僅有Phone、還有Wear、TV、Auto等。對這些不同形態的程式進行效能優化,都離不開記憶體除錯這個步驟。

相關文章