Google發佈了Android性能優化典範第2季的課程,一共20個短視頻,包括的內容大體有:電量優化,網絡優化,Wear上如何作優化,使用對象池來提升效率,LRU Cache,Bitmap的縮放,緩存,重用,PNG壓縮,自定義View的性能,提高設置alpha以後View的渲染性能,以及Lint,StictMode等等工具的使用技巧。 下面是對這些課程的總結摘要,認知有限,理解誤差的地方請多多指教!html
對於手機程序,網絡操做相對來講是比較耗電的行爲。優化網絡操做可以顯著節約電量的消耗。在性能優化第1季裏面有提到過,手機硬件的各個模塊的耗電量是不同的,其中移動蜂窩模塊對電量消耗是比較大的,另外蜂窩模塊在不一樣工做強度下,對電量的消耗也是有差別的。當程序想要執行某個網絡請求以前,須要先喚醒設備,而後發送數據請求,以後等待返回數據,最後才慢慢進入休眠狀態。這個流程以下圖所示:java
在上面那個流程中,蜂窩模塊的電量消耗差別以下圖所示:android
從圖示中能夠看到,激活瞬間,發送數據的瞬間,接收數據的瞬間都有很大的電量消耗,因此,咱們應該從如何傳遞網絡數據以及什麼時候發起網絡請求這兩個方面來着手優化。git
首先咱們須要區分哪些網絡請求是須要及時返回結果的,哪些是能夠延遲執行的。例如,用戶主動下拉刷新列表,這種行爲須要當即觸發網絡請求,並等待數據返回。可是對於上傳用戶操做的數據,同步程序設置等等行爲則屬於能夠延遲的行爲。咱們能夠經過Battery Historian這個工具來查看關於移動蜂窩模塊的電量消耗(關於這部分的細節,請點擊Android性能優化之電量篇)。在Mobile Radio那一行會顯示蜂窩模塊的電量消耗狀況,紅色的部分表明模塊正在工做,中間的間隔部分表明模塊正在休眠狀態,若是看到有一段區間,紅色與間隔頻繁的出現,那就說明這裏有能夠優化的行爲。以下圖所示:程序員
對於上面能夠優化的部分,咱們能夠有針對性的把請求行爲捆綁起來,延遲到某個時刻統一發起請求。以下圖所示:github
通過上面的優化以後,咱們再回頭使用Battery Historian導出電量消耗圖,能夠看到喚醒狀態與休眠狀態是連續大塊間隔的,這樣的話,整體電量的消耗就會變得更少。web
固然,咱們甚至能夠把請求的任務延遲到手機網絡切換到WiFi,手機處於充電狀態下再執行。在前面的描述過程當中,咱們會遇到的一個難題是如何把網絡請求延遲,並批量進行執行。還好,Android提供了JobScheduler來幫助咱們達成這個目標。算法
關於這部分主要會涉及到Prefetch(預取)與Compressed(壓縮)這兩個技術。對於Prefetch的使用,咱們須要預先判斷用戶在這次操做以後,後續零散的請求是否頗有可能會立刻被觸發,能夠把後面5分鐘有可能會使用到的零散請求都一次集中執行完畢。對於Compressed的使用,在上傳與下載數據以前,使用CPU對數據進行壓縮與解壓,能夠很大程度上減小網絡傳輸的時間。編程
想要知道咱們的應用程序中網絡請求發生的時間,每次請求的數據量等等信息,能夠經過Android Studio中的Networking Traffic Tool來查看詳細的數據,以下圖所示:緩存
在Android Wear上會大量的使用Sensors來實現某些特殊功能,如何在儘可能節約電量的前提下利用好Sensor會是咱們須要特別注意的問題。下面會介紹一些在Android Wear上的最佳實踐典範。
儘可能減小刷新請求,例如咱們能夠在不須要某些數據的時候儘快註銷監聽,減少刷新頻率,對Sensor的數據作批量處理等等。那麼如何作到這些優化呢?
更對關於Sensors的知識,能夠點擊這裏
Android Material Design風格的應用採用了大量的動畫來進行UI切換,優化動畫的性能不只可以提高用戶體驗還能夠減小電量的消耗,下面會介紹一些簡單易行的方法。
在Android裏面一個相對操做比較繁重的事情是對Bitmap進行旋轉,縮放,裁剪等等。例如在一個圓形的鐘表圖上,咱們把時鐘的指針摳出來當作單獨的圖片進行旋轉會比旋轉一張完整的圓形圖的所造成的幀率要高56%。
另外儘可能減小每次重繪的元素能夠極大的提高性能,假如某個鐘錶界面上有不少須要顯示的複雜組件,咱們能夠把這些組件作拆分處理,例如把背景圖片單獨拎出來設置爲一個獨立的View,經過setLayerType()方法使得這個View強制用Hardware來進行渲染。至於界面上哪些元素須要作拆分,他們各自的更新頻率是多少,須要有針對性的單獨討論。
如何使用Systrace等工具來查看某些View的渲染性能,在前面的章節裏面有提到過,感興趣的能夠點擊這裏
對於大多數應用中的動畫,咱們會使用PropertyAnimation或者ViewAnimation來操做實現,Android系統會自動對這些Animation作必定的優化處理,在Android上面學習到的大多數性能優化的知識一樣也適用於Android Wear。
想要獲取更多關於Android Wear中動畫效果的優化,請點擊WatchFace這個範例。
在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的性能與電量:
在程序裏面常常會遇到的一個問題是短期內建立大量的對象,致使內存緊張,從而觸發GC致使性能問題。對於這個問題,咱們可使用對象池技術來解決它。一般對象池中的對象多是bitmaps,views,paints等等。關於對象池的操做原理,不展開述說了,請看下面的圖示:
使用對象池技術有不少好處,它能夠避免內存抖動,提高性能,可是在使用的時候有一些內容是須要特別注意的。一般狀況下,初始化的對象池裏面都是空白的,當使用某個對象的時候先去對象池查詢是否存在,若是不存在則建立這個對象而後加入對象池,可是咱們也能夠在程序剛啓動的時候就事先爲對象池填充一些即將要使用到的數據,這樣能夠在須要使用到這些對象的時候提供更快的首次加載速度,這種行爲就叫作預分配。使用對象池也有很差的一面,程序員須要手動管理這些對象的分配與釋放,因此咱們須要慎重地使用這項技術,避免發生對象的內存泄漏。爲了確保全部的對象可以正確被釋放,咱們須要保證加入對象池的對象和其餘外部對象沒有互相引用的關係。
遍歷容器是編程裏面一個常常遇到的場景。在Java語言中,使用Iterate是一個比較常見的方法。但是在Android開發團隊中,你們卻儘可能避免使用Iterator來執行遍歷操做。下面咱們看下在Android上可能用到的三種不一樣的遍歷方法:
使用上面三種方式在同一臺手機上,使用相同的數據集作測試,他們的表現性能以下所示:
從上面能夠看到for index的方式有更好的效率,可是由於不一樣平臺編譯器優化各有差別,咱們最好仍是針對實際的方法作一下簡單的測量比較好,拿到數據以後,再選擇效率最高的那個方式。
這小節咱們要討論的是緩存算法,在Android上面最經常使用的一個緩存算法是LRU(Least Recently Use),關於LRU算法,不展開述說,用下面一張圖演示下含義:
LRU Cache的基礎構建用法以下:
爲了給LRU Cache設置一個比較合理的緩存大小值,咱們一般是用下面的方法來作界定的:
使用LRU Cache時爲了可以讓Cache知道每一個加入的Item的具體大小,咱們須要Override下面的方法:
使用LRU Cache可以顯著提高應用的性能,但是也須要注意LRU Cache中被淘汰對象的回收,否者會引發嚴重的內存泄露。
Lint是Android提供的一個靜態掃描應用源碼並找出其中的潛在問題的一個強大的工具。
例如,若是咱們在onDraw方法裏面執行了new對象的操做,Lint就會提示咱們這裏有性能問題,並提出對應的建議方案。Lint已經集成到Android Studio中了,咱們能夠手動去觸發這個工具,點擊工具欄的Analysis -> Inspect Code,觸發以後,Lint會開始工做,並把結果輸出到底部的工具欄,咱們能夠逐個查看緣由並根據指示作相應的優化修改。
Lint的功能很是強大,他可以掃描各類問題。固然咱們能夠經過Android Studio設置找到Lint,對Lint作一些定製化掃描的設置,能夠選擇忽略掉那些不想Lint去掃描的選項,咱們還能夠針對部分掃描內容修改它的提示優先級。
建議把與內存有關的選項中的嚴重程度標記爲紅色的Error,對於Layout的性能問題標記爲黃色Warning。
這小節會介紹如何減小透明區域對性能的影響。一般來講,對於不透明的View,顯示它只須要渲染一次便可,但是若是這個View設置了alpha值,會至少須要渲染兩次。緣由是包含alpha的view須要事先知道混合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內存空間,咱們能夠作下面的設置。
經過上面的設置之後,性能能夠獲得顯著的提高,以下圖所示:
咱們都知道應該避免在onDraw()方法裏面執行致使內存分配的操做,下面講解下爲什麼須要這樣作。
首先onDraw()方法是執行在UI線程的,在UI線程儘可能避免作任何可能影響到性能的操做。雖然分配內存的操做並不須要花費太多系統資源,可是這並不意味着是免費無代價的。設備有必定的刷新頻率,致使View的onDraw方法會被頻繁的調用,若是onDraw方法效率低下,在頻繁刷新累積的效應下,效率低的問題會被擴大,而後會對性能有嚴重的影響。
若是在onDraw裏面執行內存分配的操做,會容易致使內存抖動,GC頻繁被觸發,雖然GC後來被改進爲執行在另一個後臺線程(GC操做在2.3之前是同步的,以後是併發),但是頻繁的GC的操做仍是會影響到CPU,影響到電量的消耗。
那麼簡單解決頻繁分配內存的方法就是把分配操做移動到onDraw()方法外面,一般狀況下,咱們會把onDraw()裏面new Paint的操做移動到外面,以下面所示:
UI線程被阻塞超過5秒,就會出現ANR,這太糟糕了。防止程序出現ANR是很重要的事情,那麼如何找出程序裏面潛在的坑,預防ANR呢?不少大部分狀況下執行很快的方法,可是他們有可能存在巨大的隱患,這些隱患的爆發就很容易致使ANR。
Android提供了一個叫作Strict Mode的工具,咱們能夠經過手機設置裏面的開發者選項,打開Strict Mode選項,若是程序存在潛在的隱患,屏幕就會閃現紅色。咱們也能夠經過StrictMode API在代碼層面作細化的跟蹤,能夠設置StrictMode監聽那些潛在問題,出現問題時如何提醒開發者,能夠對屏幕閃紅色,也能夠輸出錯誤日誌。下面是官方的代碼示例:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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(); } |
Android系統有提供超過70多種標準的View,例如TextView,ImageView,Button等等。在某些時候,這些標準的View沒法知足咱們的須要,那麼就須要咱們本身來實現一個View,這節會介紹如何優化自定義View的性能。
一般來講,針對自定義View,咱們可能犯下面三個錯誤:
最後請時刻牢記,儘可能提升View的繪製性能,這樣才能保證界面的刷新幀率儘可能的高。更多關於這部分的內容,能夠看這裏
優化性能時大多數時候討論的都是如何減小沒必要要的操做,可是選擇什麼時候去執行某些操做一樣也很重要。在第1季以及上一期的性能優化之電量篇裏面,咱們有提到過移動蜂窩模塊的電量消耗模型。爲了不咱們的應用程序過多的頻繁消耗電量,咱們須要學習如何把後臺任務打包批量,並選擇一個合適的時機進行觸發執行。下圖是每一個應用程序各自執行後臺任務致使的電量消耗示意圖:
由於像上面那樣作會致使浪費不少電量,咱們須要作的是把部分應用的任務延遲處理,等到必定時機,這些任務一併進行處理。結果以下面的示意圖:
執行延遲任務,一般有下面三種方式:
使用AlarmManager設置定時任務,能夠選擇精確的間隔時間,也能夠選擇非精確時間做爲參數。除非程序有很強烈的須要使用精確的定時喚醒,否者必定要避免使用他,咱們應該儘可能使用非精確的方式。
咱們可使用SyncAdapter爲應用添加設置帳戶,這樣在手機設置的帳戶列表裏面能夠找到咱們的應用。這種方式功能更多,可是實現起來比較複雜。咱們能夠從這裏看到官方的培訓課程:http://developer.android.com/training/sync-adapters/index.html
這是最簡單高效的方法,咱們能夠設置任務延遲的間隔,執行條件,還能夠增長重試機制。
常見的png,jpeg,webp等格式的圖片在設置到UI上以前須要通過解碼的過程,而解壓時能夠選擇不一樣的解碼率,不一樣的解碼率對內存的佔用是有很大差異的。在不影響到畫質的前提下儘可能減小內存的佔用,這可以顯著提高應用程序的性能。
Android的Heap空間是不會自動作兼容壓縮的,意思就是若是Heap空間中的圖片被收回以後,這塊區域並不會和其餘已經回收過的區域作從新排序合併處理,那麼當一個更大的圖片須要放到heap以前,極可能找不到那麼大的連續空閒區域,那麼就會觸發GC,使得heap騰出一塊足以放下這張圖片的空閒區域,若是沒法騰出,就會發生OOM。以下圖所示:
因此爲了不加載一張超大的圖片,須要儘可能減小這張圖片所佔用的內存大小,Android爲圖片提供了4種解碼格式,他們分別佔用的內存大小以下圖所示:
隨着解碼佔用內存大小的下降,清晰度也會有損失。咱們須要針對不一樣的應用場景作不一樣的處理,大圖和小圖能夠採用不一樣的解碼率。在Android裏面能夠經過下面的代碼來設置解碼率:
儘可能減小PNG圖片的大小是Android裏面很重要的一條規範。相比起JPEG,PNG可以提供更加清晰無損的圖片,可是PNG格式的圖片會更大,佔用更多的磁盤空間。究竟是使用PNG仍是JPEG,須要設計師仔細衡量,對於那些使用JPEG就能夠達到視覺效果的,能夠考慮採用JPEG便可。咱們能夠經過Google搜索到不少關於PNG壓縮的工具,以下圖所示:
這裏要介紹一種新的圖片格式:Webp,它是由Google推出的一種既保留png格式的優勢,又可以減小圖片大小的一種新型圖片格式。關於Webp的更多細節,請點擊這裏
對bitmap作縮放,這也是Android裏面最遇到的問題。對bitmap作縮放的意義很明顯,提示顯示性能,避免分配沒必要要的內存。Android提供了現成的bitmap縮放的API,叫作createScaledBitmap(),使用這個方法能夠獲取到一張通過縮放的圖片。
上面的方法可以快速的獲得一張通過縮放的圖片,但是這個方法可以執行的前提是,原圖片須要事先加載到內存中,若是原圖片過大,極可能致使OOM。下面介紹其餘幾種縮放圖片的方式。
inSampleSize可以等比的縮放顯示圖片,同時還避免了須要先把原圖加載進內存的缺點。咱們會使用相似像下面同樣的方法來縮放bitmap:
另外,咱們還可使用inScaled,inDensity,inTargetDensity的屬性來對解碼圖片作處理,源碼以下圖所示:
還有一個常用到的技巧是inJustDecodeBounds,使用這個屬性去嘗試解碼圖片,能夠事先獲取到圖片的大小而不至於佔用什麼內存。以下圖所示:
咱們知道bitmap會佔用大量的內存空間,這節會講解什麼是inBitmap屬性,如何利用這個屬性來提高bitmap的循環效率。前面咱們介紹過使用對象池的技術來解決對象頻繁建立再回收的效率問題,使用這種方法,bitmap佔用的內存空間會差很少是恆定的數值,每次新建立出來的bitmap都會須要佔用一塊單獨的內存區域,以下圖所示:
爲了解決上圖所示的效率問題,Android在解碼圖片的時候引進了inBitmap屬性,使用這個屬性能夠獲得下圖所示的效果:
使用inBitmap屬性能夠告知Bitmap解碼器去嘗試使用已經存在的內存區域,新解碼的bitmap會嘗試去使用以前那張bitmap在heap中所佔據的pixel data內存區域,而不是去問內存從新申請一塊區域來存放bitmap。利用這種特性,即便是上千張的圖片,也只會僅僅只須要佔用屏幕所可以顯示的圖片數量的內存大小。下面是如何使用inBitmap的代碼示例:
使用inBitmap須要注意幾個限制條件:
咱們能夠建立一個包含多種典型可重用bitmap的對象池,這樣後續的bitmap建立都可以找到合適的「模板」去進行重用。以下圖所示:
Google介紹了一個開源的加載bitmap的庫:Glide,這裏麪包含了各類對bitmap的優化技巧。
大多數開發者在沒有發現嚴重性能問題以前是不會特別花精力去關注性能優化的,一般你們關注的都是功能是否實現。當性能問題真的出現的時候,請不要慌亂。咱們一般採用下面三個步驟來解決性能問題。
咱們能夠經過Android SDK裏面提供的諸多工具來收集CPU,GPU,內存,電量等等性能數據,
經過上面的步驟,咱們獲取到了大量的數據,下一步就是分析這些數據。工具幫咱們生成了不少可讀性強的表格,咱們須要事先了解如何查看錶格的數據,每一項表明的含義,這樣纔可以快速定位問題。若是分析數據以後仍是沒有找到問題,那麼就只能不停的從新收集數據,再進行分析,如此循環。
定位到問題以後,咱們須要採起行動來解決問題。解決問題以前必定要先有個計劃,評估這個解決方案是否可行,是否可以及時的解決問題。
雖然前面介紹了不少調試的方法,處理技巧,規範建議等等,但是這並不意味着全部的狀況都適用,咱們仍是須要根據當時的情景作特定靈活的處理。
圍繞Android生態系統,不只僅有Phone,還有Wear,TV,Auto等等。對這些不一樣形態的程序進行性能優化,都離不開內存調試這個步驟。這節中介紹的內容大部分和Android性能優化典範與Android性能優化以內存篇重合,不展開了。