android app效能最佳化大匯總(google官方Android效能最佳化典範 - 第2季)

來源:互聯網
上載者:User

標籤:

  Google前幾天剛發布了Android效能最佳化典範第2季的課程,一共20個短視頻,包括的內容大致有:電量最佳化,網路最佳化,Wear上如何做最佳化,使用對象池來提高效率,LRU Cache,Bitmap的縮放,緩衝,重用,PNG壓縮,自訂View的效能,提升設定alpha之後View的渲染效能,以及Lint,StictMode等等工具的提示。

(1)Battery Drain and Networking  

  對於手機程式,網路操作相對來說是比較耗電的行為。最佳化網路操作能夠顯著節約電量的消耗。在效能最佳化第1季裡面有提到過,手機硬體的各個模組的耗電量是不一樣的,其中移動蜂窩模組對電量消耗是比較大的,另外蜂窩模組在不同工作強度下,對電量的消耗也是有差異的。當程式想要執行某個網路請求之前,需要先喚醒裝置,然後發送資料請求,之後等待返回資料,最後才慢慢進入休眠狀態。這個流程如所示:

在上面那個流程中,蜂窩模組的電量消耗差異如所示:

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

(1.1)何時發起網路請求

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

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

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

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

(1.2)如何傳遞網路資料

  關於這部分主要會涉及到Prefetch(預取)與Compressed(壓縮)這兩個技術。對於Prefetch的使用,我們需要預先判斷使用者在此次操作之後,後續零散的請求是否很有可能會馬上被觸發,可以把後面5分鐘有可能會使用到的零散請求都一次集中執行完畢。對於Compressed的使用,在上傳與下載資料之前,使用CPU對資料進行壓縮與解壓,可以很大程度上減少網路傳輸的時間。

想要知道我們的應用程式中網路請求發生的時間,每次請求的資料量等等資訊,可以通過Android Studio中的Networking Traffic Tool來查看詳細的資料,如所示:

(2)Wear & Sensors

  在Android Wear上會大量的使用Sensors來實現某些特殊功能,如何在盡量節約電量的前提下利用好Sensor會是我們需要特別注意的問題。下面會介紹一些在Android Wear上的最佳實務典範。

盡量減少重新整理請求,例如我們可以在不需要某些資料的時候儘快登出監聽,減小重新整理頻率,對Sensor的資料做批量處理等等。那麼如何做到這些最佳化呢?

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

更對關於Sensors的知識,可以點擊這裡

(3)Smooth Android Wear Animation

  Android Material Design風格的應用採用了大量的動畫來進行UI切換,最佳化動畫的效能不僅能夠提升使用者體驗還可以減少電量的消耗,下面會介紹一些簡單易行的方法。

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

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

如何使用Systrace等工具來查看某些View的渲染效能,在前面的章節裡面有提到過,感興趣的可以點擊這裡

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

想要擷取更多關於Android Wear中動畫效果的最佳化,請點擊WatchFace這個範例。

(4)Android Wear Data Batching

  在Android Training裡面有關於Wear上面如何利用Wearable API與Phone進行溝通協作的課程(詳情請點擊這裡)。因為Phone的CPU與電量都比Wear要強大,另外Phone還可以直接接入網路,而Wear要接入網路則相對更加困難,所以我們在開發Wear應用的時候需要盡量做到把複雜的操作交給Phone來執行。例如我們可以讓Phone來擷取天氣資訊,然後把資料返回Wear進行顯示。更進一步,在之前的效能最佳化課程裡面我們有學習過如何使用JobScheduler來延遲批量處理任務,假設Phone收到來自Wear的其中一個任務是每隔5分鐘檢查一次天氣情況,那麼Phone使用JobScheduler執行檢查天氣任務之後,先判斷這次返回的結果和之前是否有差異,僅僅當天氣發生變化的時候,才有必要把結果通知到Wear,或者僅僅把變化的某一項資料通知給Wear,這樣可以更大程度上減少Wear的電量消耗。

下面我們總結一下如何最佳化Wear的效能與電量:

  • 僅僅在真正需要重新整理介面的時候才發出請求
  • 盡量把計算複雜操作的任務交給Phone來處理
  • Phone僅僅在資料發生變化的時候才通知到Wear
  • 把零碎的資料請求捆綁一起再進行操作
(5)Object Pools

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

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

(6)To Index or Iterate?

遍曆容器是編程裡面一個經常遇到的情境。在Java語言中,使用Iterate是一個比較常見的方法。可是在AndroidTeam Dev中,大家卻盡量避免使用Iterator來執行遍曆操作。下面我們看下在Android上可能用到的三種不同的遍曆方法:

使用上面三種方式在同一台手機上,使用相同的資料集做測試,他們的表現效能如下所示:

從上面可以看到for index的方式有更好的效率,但是因為不同平台編譯器最佳化各有差異,我們最好還是針對實際的方法做一下簡單的測量比較好,拿到資料之後,再選擇效率最高的那個方式。

(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。

(9)Hidden Cost of Transparency

  這小節會介紹如何減少透明地區對效能的影響。通常來說,對於不透明的View,顯示它只需要渲染一次即可,可是如果這個View設定了alpha值,會至少需要渲染兩次。原因是包含alpha的view需要事Crowdsourced Security Testing道混合View的下一層元素是什麼,然後再結合上層的View進行Blend混色處理。

在某些情況下,一個包含alpha的View有可能會觸發改View在HierarchyView上的父View都被額外重繪一次。下面我們看一個例子,示範的ListView中的圖片與二級標題都有設定透明度。

大多數情況下,螢幕上的元素都是由後向前進行渲染的。在上面的圖示中,會先渲染背景圖(藍,綠,紅),然後渲染人物頭像圖。如果後渲染的元素有設定alpha值,那麼這個元素就會和螢幕上已經渲染好的元素做blend處理。很多時候,我們會給整個View設定alpha的來達到fading的動畫效果,如果我們圖示中的ListView做alpha逐漸減小的處理,我們可以看到ListView上的TextView等等組件會逐漸融合到背景色上。但是在這個過程中,我們無法觀察到它其實已經觸發了額外的繪製任務,我們的目標是讓整個View逐漸透明,可是期間ListView在不停的做Blending的操作,這樣會導致不少效能問題。

如何渲染才能夠得到我們想要的效果呢?我們可以先按照通常的方式把View上的元素按照從後到前的方式繪製出來,但是不直接顯示到螢幕上,而是使用GPU預先處理之後,再又GPU渲染到螢幕上,GPU可以對介面上的未經處理資料直接做旋轉,設定透明度等等操作。使用GPU進行渲染,雖然第一次操作相比起直接繪製到螢幕上更加耗時,可是一旦原始紋理資料產生之後,接下去的操作就比較省時省力。

如何才能夠讓GPU來渲染某個View呢?我們可以通過setLayerType的方法來指定View應該如何進行渲染,從SDK 16開始,我們還可以使用ViewPropertyAnimator.alpha().withLayer()來指定。如所示:

另外一個例子是包含陰影地區的View,這種類型的View並不會出現我們前面提到的問題,因為他們並不存在層疊的關係。

為了能夠讓渲染器知道這種情況,避免為這種View佔用額外的GPU記憶體空間,我們可以做下面的設定。

通過上面的設定以後,效能可以得到顯著的提升,如所示:

(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監聽那些潛在問題,出現問題時如何提醒開發人員,可以對螢幕閃紅色,也可以輸出錯誤記錄檔。下面是官方的程式碼範例:

1234567891011121314151617
public void onCreate() {     if (DEVELOPER_MODE) {         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()                 .detectDiskReads()                 .detectDiskWrites()                 .detectNetwork()   // or .detectAll() for all detectable problems                 .penaltyLog()                 .build());         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()                 .detectLeakedSqlLiteObjects()                 .detectLeakedClosableObjects()                 .penaltyLog()                 .penaltyDeath()                 .build());     }     super.onCreate();}
(12)Custom Views and Performance

  Android系統有提供超過70多種標準的View,例如TextView,ImageView,Button等等。在某些時候,這些標準的View無法滿足我們的需要,那麼就需要我們自己來實現一個View,這節會介紹如何最佳化自訂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的繪製效能,這樣才能保證介面的重新整理幀率盡量的高。

(13)Batching Background Work Until Later

  最佳化效能時大多數時候討論的都是如何減少不必要的操作,但是選擇何時去執行某些操作同樣也很重要。為了避免我們的應用程式過多的頻繁消耗電量,我們需要學習如何把背景工作打包批量,並選擇一個合適的時機進行觸發執行。是每個應用程式各自執行背景工作導致的電量消耗:

因為像上面那樣做會導致浪費很多電量,我們需要做的是把部分應用的任務延遲處理,等到一定時機,這些任務一併進行處理。結果如下面的:

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

(1)AlarmManager

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

(2)SyncAdapter

我們可以使用SyncAdapter為應用添加設定賬戶,這樣在手機設定的賬戶列表裡面可以找到我們的應用。這種方式功能更多,但是實現起來比較複雜。我們可以從這裡看到官方的培訓課程:http://developer.android.com/training/sync-adapters/index.html

(3)JobSchedulor

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

(14)Smaller Pixel Formats

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

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

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

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

(15)Smaller PNG Files

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

這裡要介紹一種新的圖片格式:Webp,它是由Google推出的一種既保留png格式的優點,又能夠減少圖片大小的一種新型圖片格式。關於Webp的更多細節,請點擊這裡

(16)Pre-scaling Bitmaps

對bitmap做縮放,這也是Android裡面最遇到的問題。對bitmap做縮放的意義很明顯,提示顯示效能,避免分配不必要的記憶體。Android提供了現成的bitmap縮放的API,叫做createScaledBitmap(),使用這個方法可以擷取到一張經過縮放的圖片。

上面的方法能夠快速的得到一張經過縮放的圖片,可是這個方法能夠執行的前提是,原圖片需要事先載入到記憶體中,如果原圖片過大,很可能導致OOM。下面介紹其他幾種縮放圖片的方式。

inSampleSize能夠等比的縮放顯示圖片,同時還避免了需要先把原圖載入進記憶體的缺點。我們會使用類似像下面一樣的方法來縮放bitmap:

另外,我們還可以使用inScaled,inDensity,inTargetDensity的屬性來對解碼圖片做處理,源碼如所示:

還有一個經常使用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖片,可以事先擷取到圖片的大小而不至於佔用什麼記憶體。如所示:

(17)Re-using Bitmaps

  我們知道bitmap會佔用大量的記憶體空間,這節會講解什麼是inBitmap屬性,如何利用這個屬性來提升bitmap的迴圈效率。前面我們介紹過使用對象池的技術來解決對象頻繁建立再回收的效率問題,使用這種方法,bitmap佔用的記憶體空間會差不多是恒定的數值,每次新建立出來的bitmap都會需要佔用一塊單獨的記憶體地區,如所示:

為瞭解決所示的效率問題,Android在解碼圖片的時候引進了inBitmap屬性,使用這個屬性可以得到所示的效果:

使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體地區,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所佔據的pixel data記憶體地區,而不是去問記憶體重新申請一塊地區來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用螢幕所能夠顯示的圖片數量的記憶體大小。下面是如何使用inBitmap的程式碼範例:

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

  • 在SDK 11 -> 18之間,重用的bitmap大小必須是一致的,例如給inBitmap賦值的圖片大小為100-100,那麼新申請的bitmap必須也為100-100才能夠被重用。從SDK 19開始,新申請的bitmap大小必須小於或者等於已經賦值過的bitmap大小。
  • 新申請的bitmap與舊的bitmap必須有相同的解碼格式,例如大家都是8888的,如果前面的bitmap是8888,那麼就不能支援4444與565格式的bitmap了。

我們可以建立一個包含多種典型可重用bitmap的對象池,這樣後續的bitmap建立都能夠找到合適的“模板”去進行重用。如所示:

Google介紹了一個開源的載入bitmap的庫:Glide,這裡麵包含了各種對bitmap的最佳化技巧。

(18)The Performance Lifecycle

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

Gather:收集資料

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

Insight:分析資料

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

Action:解決問題

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

(19)Tools not Rules

雖然前面介紹了很多調試的方法,處理技巧,規範建議等等,可是這並不意味著所有的情況都適用,我們還是需要根據當時的情景做特定靈活的處理。

 

android app效能最佳化大匯總(google官方Android效能最佳化典範 - 第2季)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.