轉——Android應用開發性能優化徹底分析

【工匠若水 http://blog.csdn.net/yanbober 轉載請註明出處。】javascript

1 背景

其實有點不想寫這篇文章的,可是又想寫,有些矛盾。不想寫的緣由是隨便上網一搜一堆關於性能的建議,感受你們你一總結、我一總結的都說到了不少優化注意事項,可是看過這些文章後大多數存在一個問題就是隻給出啥啥啥不能用,啥啥啥該咋用等,卻不多有較爲系統的進行真正性能案例分析的,大多數都是嘴上喊喊或者死記住規則而已(固然了,這話我本身聽着都有些刺耳,實在很差意思,其實關於性能優化的優質博文網上也仍是有不少的,譬如Google官方都已經推出了優化專題,我這裏只是總結下自的感悟而已,如有得罪歡迎拍磚,我願捱打,由於我工做的一半時間都是負責性能優化)。css

固然了,本文不會就此編輯這麼一次,由於技術在發展,工具在強大(寫着寫着Android Studio 1.4版本都推送了),本身的經驗也在增長,因此本文天然不會覆蓋全部性能優化及分析;解決的辦法就是該文章會長期維護更新,同時在評論區歡迎你關於性能優化點子的探討。html

Android應用的性能問題其實能夠劃分爲幾個大的模塊的,並且都具備相對不錯的優化調試技巧,下面咱們就會依據一個項目常規開發的大類型來進行一些分析講解。java

PS:以前呆過一家初創醫療互聯網公司,別提性能優化了,老闆立完新項目後一個月就要求見到上線成品,這種壓迫下談何性能優化,純屬扯蛋,因此不到三個月時間我主動選擇撤了,這種現象後來我一打聽發如今不少初創公司都很嚴重,都想速成卻忽略了體驗。python

PPPS:本文只是達到拋磚引玉的做用,不少東西細究下去都是值得深刻研究的,再加上性能優化原本就是一個須要綜合考量的任務,不是說會了本文哪一點就能作性能分析了,須要面面俱到纔可高效定位問題緣由。android

 

2 應用UI性能問題分析

UI可謂是一個應用的臉,因此每一款應用在開發階段咱們的交互、視覺、動畫工程師都拼命的想讓它變得天然大方美麗,但是現實老是不盡人意,動畫和交互總會以爲開發作出來的應用用上去感受不天然,沒有達到他們心目中的天然流暢細節;這種狀況之下就更別提發佈給終端用戶使用了,用戶要是可以感受出來,少則影響心情,多則卸載應用;因此一個應用的UI顯示性能問題就不得不被開發人員重視。shell

2-1 應用UI卡頓原理

人類大腦與眼睛對一個畫面的連貫性感知實際上是有一個界限的,譬如咱們看電影會以爲畫面很天然連貫(幀率爲24fps),用手機固然也須要感知屏幕操做的連貫性(尤爲是動畫過分),因此Android索性就把達到這種流暢的幀率規定爲60fps。數據庫

有了上面的背景,咱們開發App的幀率性能目標就是保持在60fps,也就是說咱們在進行App性能優化時心中要有以下準則:canvas

?
1
2
3
<code brainfuck= "" class = "hljs" >換算關係: 60 幀/秒-----------16ms/幀;
 
準則:儘可能保證每次在16ms內處理完全部的CPU與GPU計算、繪製、渲染等操做,不然會形成丟幀卡頓問題。</code>

從上面能夠看出來,所謂的卡頓實際上是能夠量化的,每次是否可以成功渲染是很是重要的問題,16ms可否完整的作完一次操做直接決定了卡頓性能問題。設計模式

固然了,針對Android系統的設計咱們還須要知道另外一個常識;虛擬機在執行GC垃圾回收操做時全部線程(包括UI線程)都須要暫停,當GC垃圾回收完成以後全部線程纔可以繼續執行(這個細節下面小節會有詳細介紹)。也就是說當在16ms內進行渲染等操做時若是恰好趕上大量GC操做則會致使渲染時間明顯不足,也就從而致使了丟幀卡頓問題。

有了上面這兩個簡單的理論基礎以後咱們下面就會探討一些UI卡頓的緣由分析及解決方案。

2-2 應用UI卡頓常見緣由

咱們在使用App時會發現有些界面啓動卡頓、動畫不流暢、列表等滑動時也會卡頓,究其緣由,不少都是丟幀致使的;經過上面卡頓原理的簡單說明咱們從應用開發的角度往回推理能夠得出常見卡頓緣由,以下:

人爲在UI線程中作輕微耗時操做,致使UI線程卡頓;

佈局Layout過於複雜,沒法在16ms內完成渲染;

同一時間動畫執行的次數過多,致使CPU或GPU負載太重;

View過分繪製,致使某些像素在同一幀時間內被繪製屢次,從而使CPU或GPU負載太重;

View頻繁的觸發measure、layout,致使measure、layout累計耗時過多及整個View頻繁的從新渲染;

內存頻繁觸發GC過多(同一幀中頻繁建立內存),致使暫時阻塞渲染操做;

冗餘資源及邏輯等致使加載和執行緩慢;

臭名昭著的ANR;

能夠看見,上面這些致使卡頓的緣由都是咱們平時開發中很是常見的。有些人可能會以爲本身的應用用着還蠻OK的,其實那是由於你沒進行一些瞬時測試和壓力測試,一旦在這種環境下運行你的App你就會發現不少性能問題。

2-3 應用UI卡頓分析解決方法

分析UI卡頓咱們通常都藉助工具,經過工具通常均可以直觀的分析出問題緣由,從而反推尋求優化方案,具體以下細說各類強大的工具。

2-3-1 使用HierarchyViewer分析UI性能

咱們能夠經過SDK提供的工具HierarchyViewer來進行UI佈局複雜程度及冗餘等分析,以下:

 
1
<code class = "hljs" ruby= "" >xxx @ThinkPad :~$ hierarchyviewer   //經過命令啓動HierarchyViewer</code>

選中一個Window界面item,而後點擊右上方Hierarchy window或者Pixel Perfect window(這裏不介紹,主要用來檢查像素屬性的)便可操做。

先看下Hierarchy window,以下:

這裏寫圖片描述

一個Activity的View樹,經過這個樹能夠分析出View嵌套的冗餘層級,左下角能夠輸入View的id直接自動跳轉到中間顯示;Save as PNG用來把左側樹保存爲一張圖片;Capture Layers用來保存psd的PhotoShop分層素材;右側劇中顯示選中View的當前屬性狀態;右下角顯示當前View在Activity中的位置等;左下角三個進行切換;Load View Hierarchy用來手動刷新變化(不會自動刷新的)。當咱們選擇一個View後會以下圖所示:

這裏寫圖片描述

相似上圖能夠很方便的查看到當前View的許多信息;上圖最底那三個彩色原點表明了當前View的性能指標,從左到右依次表明測量、佈局、繪製的渲染時間,紅色和黃色的點表明速度渲染較慢的View(固然了,有些時候較慢不表明有問題,譬如ViewGroup子節點越多、結構越複雜,性能就越差)。

固然了,在自定義View的性能調試時,HierarchyViewer上面的invalidate Layout和requestLayout按鈕的功能更增強大,它能夠幫助咱們debug自定義View執行invalidate()和requestLayout()過程,咱們只須要在代碼的相關地方打上斷點就好了,接下來經過它觀察繪製便可。

能夠發現,有了HierarchyViewer調試工具,咱們的UI性能分析變得十分容易,這個工具也是咱們開發中調試UI的利器,在平時寫代碼時會時常伴隨咱們左右。

2-3-2 使用GPU過分繪製分析UI性能

咱們對於UI性能的優化還能夠經過開發者選項中的GPU過分繪製工具來進行分析。在設置->開發者選項->調試GPU過分繪製(不一樣設備可能位置或者叫法不一樣)中打開調試後能夠看見以下圖(對settings當前界面過分繪製進行分析):

這裏寫圖片描述

能夠發現,開啓後在咱們想要調試的應用界面中能夠看到各類顏色的區域,具體含義以下:

顏色 含義
無色 WebView等的渲染區域
藍色 1x過分繪製
綠色 2x過分繪製
淡紅色 3x過分繪製
紅色 4x(+)過分繪製


因爲過分繪製指在屏幕的一個像素上繪製屢次(譬如一個設置了背景色的TextView就會被繪製兩次,一次背景一次文本;這裏須要強調的是Activity設置的Theme主題的背景不被算在過分繪製層級中),因此最理想的就是繪製一次,也就是藍色(固然這在不少絢麗的界面是不現實的,因此你們有個度便可,咱們的開發性能優化標準要求最極端界面下紅色區域不能長期持續超過屏幕三分之一,可見仍是比較寬鬆的規定),所以咱們須要依據此顏色分佈進行代碼優化,譬如優化佈局層級、減小不必的背景、暫時不顯示的View設置爲GONE而不是INVISIBLE、自定義View的onDraw方法設置canvas.clipRect()指定繪製區域或經過canvas.quickreject()減小繪製區域等。

2-3-3 使用GPU呈現模式圖及FPS考覈UI性能

Android界面流暢度除過視覺感知之外是能夠考覈的(測試妹子專用),常見的方法就是經過GPU呈現模式圖或者實時FPS顯示進行考覈,這裏咱們主要針對GPU呈現模式圖進行下說明,由於FPS考覈測試方法有不少(譬如本身寫代碼實現、第三方App測試、固件支持等),因此不作統一說明。

經過開發者選項中GPU呈現模式圖工具來進行流暢度考量的流程是(注意:若是是在開啓應用後纔開啓此功能,記得先把應用結束後從新啓動)在設置->開發者選項->GPU呈現模式(不一樣設備可能位置或者叫法不一樣)中打開調試後能夠看見以下圖(對settings當前界面上下滑動列表後的圖表):

這裏寫圖片描述

固然,也能夠在執行完UI滑動操做後在命令行輸入以下命令查看命令行打印的GPU渲染數據(分析依據:Draw + Process + Execute = 完整的顯示一幀時間 < 16ms):

 
1
<code class = "hljs" css= "" >adb shell dumpsys gfxinfo [應用包名]</code>

打開上圖可視化工具後,咱們能夠在手機畫面上看到豐富的GPU繪製圖形信息,分別展現了StatusBar、NavgationBar、Activity區域等的GPU渲染時間信息,隨着界面的刷新,界面上會以實時柱狀圖來顯示每幀的渲染時間,柱狀圖越高表示渲染時間越長,每一個柱狀圖偏上都有一根表明16ms基準的綠色橫線,每一條豎着的柱狀線都包含三部分(藍色表明測量繪製Display List的時間,紅色表明OpenGL渲染Display List所須要的時間,黃色表明CPU等待GPU處理的時間),只要咱們每一幀的總時間低於基準線就不會發生UI卡頓問題(個別超出基準線其實也不算啥問題的)。

能夠發現,這個工具是有侷限性的,他雖然可以看出來有幀耗時超過基準線致使了丟幀卡頓,但卻分析不到形成丟幀的具體緣由。因此說爲了配合解決分析UI丟幀卡頓問題咱們還須要藉助traceview和systrace來進行緣由追蹤,下面咱們會介紹這兩種工具的。

2-3-4 使用Lint進行資源及冗餘UI佈局等優化

上面說了,冗餘資源及邏輯等也可能會致使加載和執行緩慢,因此咱們就來看看Lint這個工具是如何發現優化這些問題的(固然了,Lint實際的功能是很是強大的,咱們開發中也是常用它來發現一些問題的,這裏主要有點針對UI性能的說明了,其餘的雷同)。

在Android Studio 1.4版本中使用Lint最簡單的辦法就是將鼠標放在代碼區點擊右鍵->Analyze->Inspect Code–>界面選擇你要檢測的模塊->點擊確認開始檢測,等待一下後會發現以下結果:

這裏寫圖片描述

能夠看見,Lint檢測完後給了咱們不少建議的,咱們重點看一個關於UI性能的檢測結果;上圖中高亮的那一行明確說明了存在冗餘的UI層級嵌套,因此咱們是能夠點擊跳進去進行優化處理掉的。

固然了,Lint還有不少功能,你們能夠自行探索發揮,這裏只是達到拋磚引玉的做用。

2-3-5 使用Memory監測及GC打印與Allocation Tracker進行UI卡頓分析

關於Android的內存管理機制下面的一節會詳細介紹,這裏咱們主要針對GC致使的UI卡頓問題進行詳細說明。

Android系統會依據內存中不一樣的內存數據類型分別執行不一樣的GC操做,常見應用開發中致使GC頻繁執行的緣由主要多是由於短期內有大量頻繁的對象建立與釋放操做,也就是俗稱的內存抖動現象,或者短期內已經存在大量內存暫用介於閾值邊緣,接着每當有新對象建立時都會致使超越閾值觸發GC操做。

以下是我工做中一個項目的一次經歷(我將代碼回退特地抓取的),出現這個問題的場景是一次壓力測試致使整個系統卡頓,瞬間殺掉應用就OK了,究其緣由最終查到是一個API的調運位置寫錯了方式,致使一直被狂調,當普通使用時不會有問題,壓力測試必現卡頓。具體內存參考圖以下:
這裏寫圖片描述

與此抖動圖對應的LogCat抓取以下:

1
2
3
4
5
6
7
<code asciidoc= "" class = "hljs" > //截取其中比較密集一段LogCat,與上圖Memory檢測到的抖動圖對應,其中xxx爲應用包名
......
10 - 06 00 : 59 : 45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515 (3MB) AllocSpace objects, 65 (2028KB) LOS objects, 80 % free, 17MB/89MB, paused 3 .505ms total 60 .958ms
10 - 06 00 : 59 : 45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396 (193KB) AllocSpace objects, 0 (0B) LOS objects, 75 % free, 23MB/95MB, paused 2 .079ms total 100 .522ms
......
10 - 06 00 : 59 : 48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693 (172KB) AllocSpace objects, 0 (0B) LOS objects, 75 % free, 23MB/95MB, paused 2 .227ms total 101 .692ms
......</code>

咱們知道,相似上面logcat打印同樣,觸發垃圾回收的主要緣由有如下幾種:

GC_MALLOC——內存分配失敗時觸發;

GC_CONCURRENT——當分配的對象大小超過384K時觸發;

GC_EXPLICIT——對垃圾收集的顯式調用(System.gc()) ;

GC_EXTERNAL_ALLOC——外部內存分配失敗時觸發;

能夠看見,這種不停的大面積打印GC致使全部線程暫停的操做一定會致使UI視覺的卡頓,因此咱們要避免此類問題的出現,具體的常見優化方式以下:

檢查代碼,儘可能避免有些頻繁觸發的邏輯方法中存在大量對象分配; 儘可能避免在屢次for循環中頻繁分配對象; 避免在自定義View的onDraw()方法中執行復雜的操做及建立對象(譬如Paint的實例化操做不要寫在onDraw()方法中等); 對於併發下載等相似邏輯的實現儘可能避免屢次建立線程對象,而是交給線程池處理。

固然了,有了上面說明GC致使的性能後咱們就該定位分析問題了,能夠經過運行DDMS->Allocation Tracker標籤打開一個新窗口,而後點擊Start Tracing按鈕,接着運行你想分析的代碼,運行完畢後點擊Get Allocations按鈕就可以看見一個已分配對象的列表,以下:

這裏寫圖片描述

點擊上面第一個表格中的任何一項就可以在第二個表格中看見致使該內存分配的棧信息,經過這個工具咱們能夠很方便的知道代碼分配了哪類對象、在哪一個線程、哪一個類、哪一個文件的哪一行。譬如咱們能夠經過Allocation Tracker分別作一次Paint對象實例化在onDraw與構造方法的一個自定義View的內存跟蹤,而後你就明白這個工具的強大了。

PS一句,Android Studio新版本除過DDMS之外在Memory視圖的左側已經集成了Allocation Tracker功能,只是用起來仍是沒有DDMS的方便實用,以下圖:

這裏寫圖片描述

2-3-6 使用Traceview和dmtracedump進行分析優化

關於UI卡頓問題咱們還能夠經過運行Traceview工具進行分析,他是一個分析器,記錄了應用程序中每一個函數的執行時間;咱們能夠打開DDMS而後選擇一個進程,接着點擊上面的「Start Method Profiling」按鈕(紅色小點變爲黑色即開始運行),而後操做咱們的卡頓UI(小範圍測試,因此操做最好不要超過5s),完事再點一下剛纔按的那個按鈕,稍等片刻便可出現下圖,以下:

這裏寫圖片描述

花花綠綠的一幅圖咱們怎麼分析呢?下面咱們解釋下如何經過該工具定位問題:

整個界面包括上下兩部分,上面是你測試的進程中每一個線程運行的時間線,下面是每一個方法(包含parent及child)執行的各個指標的值。經過上圖的時間面板能夠直觀發現,整個trace時間段main線程作的事情特別多,其餘的作的相對較少。當咱們選擇上面的一個線程後能夠發現下面的性能面板很複雜,其實這纔是TraceView的核心圖表,它主要展現了線程中各個方法的調用信息(CPU使用時間、調用次數等),這些信息就是咱們分析UI性能卡頓的核心關注點,因此咱們先看幾個重要的屬性說明,以下:

屬性名 含義
name 線程中調運的方法名;
Incl CPU Time 當前方法(包含內部調運的子方法)執行佔用的CPU時間;
Excl CPU Time 當前方法(不包含內部調運的子方法)執行佔用的CPU時間;
Incl Real Time 當前方法(包含內部調運的子方法)執行的真實時間,ms單位;
Excl Real Time 當前方法(不包含內部調運的子方法)執行的真實時間,ms單位;
Calls+Recur Calls/Total 當前方法被調運的次數及遞歸調運佔總調運次數百分比;
CPU Time/Call 當前方法調運CPU時間與調運次數比,即當前方法平均執行CPU耗時時間;
Real Time/Call 當前方法調運真實時間與調運次數比,即當前方法平均執行真實耗時時間;(重點關注)


有了對上面Traceview圖表的一個認識以後咱們就來看看具體致使UI性能後該如何切入分析,通常Traceview能夠定位兩類性能問題:

方法調運一次須要耗費很長時間致使卡頓;

方法調運一次耗時不長,但被頻繁調運致使累計時長卡頓。

譬如咱們來舉個實例,有時候咱們寫完App在使用時不以爲有啥大的影響,可是當咱們啓動完App後靜止在那卻十分費電或者致使設備發熱,這種狀況咱們就能夠打開Traceview而後按照Cpu Time/Call或者Real Time/Call進行降序排列,而後打開可疑的方法及其child進行分析查看,而後再回到代碼定位檢查邏輯優化便可;固然了,咱們也能夠經過該工具來trace咱們自定義View的一些方法來權衡性能問題,這裏再也不一一列舉嘍。

能夠看見,Traceview可以幫助咱們分析程序性能,已經很方便了,然而Traceview家族還有一個更加直觀強大的小工具,那就是能夠經過dmtracedump生成方法調用圖。具體作法以下:

 
1
<code class = "hljs" rsl= "" >dmtracedump -g result.png target.trace  //結果png文件  目標trace文件</code>

經過這個生成的方法調運圖咱們能夠更加直觀的發現一些方法的調運異常現象。不過本人優化到如今還沒怎麼用到它,每次用到Traceview分析就已經搞定問題了,因此說dmtracedump本身酌情使用吧。

PS一句,Android Studio新版本除過DDMS之外在CPU視圖的左側已經集成了Traceview(start Method Tracing)功能,只是用起來仍是沒有DDMS的方便實用(這裏有一篇AS MT我的以爲不錯的分析文章(引用自網絡,連接屬於原做者功勞)),以下圖:

這裏寫圖片描述

2-3-7 使用Systrace進行分析優化

Systrace其實有些相似Traceview,它是對整個系統進行分析(同一時間軸包含應用及SurfaceFlinger、WindowManagerService等模塊、服務運行信息),不過這個工具須要你的設備內核支持trace(命令行檢查/sys/kernel/debug/tracing)且設備是eng或userdebug版本才能夠,因此使用前麻煩本身確認一下。

咱們在分析UI性能時通常只關注圖形性能(因此必須選擇Graphics和View,其餘隨意),同時通常對於卡頓的抓取都是5s,最多10s。啓動Systrace進行數據抓取能夠經過兩種方式,命令行方式以下:

?
1
<code avrasm= "" class = "hljs" >python systrace.py --time= 10 -o mynewtrace.html sched gfx view wm</code>

圖形模式:
打開DDMS->Capture system wide trace using Android systrace->設置時間與選項點擊OK就開始了抓取,接着操做APP,完事生成一個trace.html文件,用Chrome打開便可以下圖:

這裏寫圖片描述
在Chrome中瀏覽分析該文件咱們能夠經過鍵盤的W-A-S-D鍵來搞定,因爲上面咱們在進行trace時選擇了一些選項,因此上圖生成了左上方相關的CPU頻率、負載、狀態等信息,其中的CPU N表明了CPU核數,每一個CPU行的柱狀圖表表明了當前時間段當前核上的運行信息;下面咱們再來看看SurfaceFlinger的解釋,以下:

這裏寫圖片描述

能夠看見上面左邊欄的SurfaceFlinger其實就是負責繪製Android程序UI的服務,因此SurfaceFlinger能反應出總體繪製狀況,能夠關注上圖VSYNC-app一行能夠發現前5s多基本都可以達到16ms刷新間隔,5s多開始到7s多大於了15ms,說明此時存在繪製丟幀卡頓;同時能夠發現surfaceflinger一行明顯存在相似不規律間隔,這是由於有的地方是不須要從新渲染UI,因此有大範圍不規律,有的是由於阻塞致使不規律,明顯能夠發現0到4s間大可能是不須要渲染,而5s之後大可能是阻塞致使;對應這個時間點咱們放大能夠看到每一個部分所使用的時間和正在執行的任務,具體以下:
這裏寫圖片描述

能夠發現具體的執行明顯存在超時性能卡頓(原點不是綠色的基本都表明存在必定問題,下面和右側都會提示你選擇的幀相關詳細信息或者alert信息),可是遺憾的是經過Systrace只能大致上發現是否存在性能問題,具體問題還須要經過Traceview或者代碼中嵌入Trace工具類等去繼續詳細分析,總之很蛋疼。

PS:若是你想使用Systrace很輕鬆的分析定位全部問題,看明白全部的行含義,你還須要具有很是紮實的Android系統框架的原理才能夠將該工具使用的駕輕就熟。

2-3-8 使用traces.txt文件進行ANR分析優化

ANR(Application Not Responding)是Android中AMS與WMS監測應用響應超時的表現;之因此把臭名昭著的ANR單獨做爲UI性能卡頓的分析來講明是由於ANR是直接卡死UI不動且必需要解掉的Bug,咱們必須儘可能在開發時避免他的出現,固然了,萬一出現了那就用下面介紹的方法來分析吧。

咱們應用開發中常見的ANR主要有以下幾類:

按鍵觸摸事件派發超時ANR,通常閾值爲5s(設置中開啓ANR彈窗,默認有事件派發纔會觸發彈框ANR);

廣播阻塞ANR,通常閾值爲10s(設置中開啓ANR彈窗,默認不彈框,只有log提示);

服務超時ANR,通常閾值爲20s(設置中開啓ANR彈窗,默認不彈框,只有log提示);

當ANR發生時除過logcat能夠看見的log之外咱們還能夠在系統指定目錄下找到traces文件或dropbox文件進行分析,發生ANR後咱們能夠經過以下命令獲得ANR trace文件:

 
1
<code class = "hljs" haskell= "" >adb pull /data/anr/traces.txt ./</code>

而後咱們用txt編輯器打開能夠發現以下結構分析:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<code avrasm= "" class = "hljs" > //顯示進程id、ANR發生時間點、ANR發生進程包名
----- pid 19073 at 2015 - 10 - 08 17 : 24 : 38 -----
Cmd line: com.example.yanbo.myapplication
//一些GC等object信息,一般能夠忽略
......
//ANR方法堆棧打印信息!重點!
DALVIK THREADS ( 18 ):
main prio= 5 tid= 1 Sleeping
   | group=main sCount= 1 dsCount= 0 obj= 0x7497dfb8 self= 0x7f9d09a000
   | sysTid= 19073 nice= 0 cgrp= default sched= 0 / 0 handle= 0x7fa106c0a8
   | state=S schedstat=( 125271779 68162762 280 ) utm= 11 stm= 1 core= 0 HZ= 100
   | stack= 0x7fe90d3000 - 0x7fe90d5000 stackSize=8MB
   | held mutexes=
   at java.lang.Thread.sleep!(Native method)
   - sleeping on < 0x0a2ae345 > (a java.lang.Object)
   at java.lang.Thread.sleep(Thread.java: 1031 )
   - locked < 0x0a2ae345 > (a java.lang.Object)
//真正致使ANR的問題點,能夠發現是onClick中有sleep致使。咱們平時能夠類比分析便可,這裏不詳細說明。
   at java.lang.Thread.sleep(Thread.java: 985 )
   at com.example.yanbo.myapplication.MainActivity$ 1 .onClick(MainActivity.java: 21 )
   at android.view.View.performClick(View.java: 4908 )
   at android.view.View$PerformClick.run(View.java: 20389 )
   at android.os.Handler.handleCallback(Handler.java: 815 )
   at android.os.Handler.dispatchMessage(Handler.java: 104 )
   at android.os.Looper.loop(Looper.java: 194 )
   at android.app.ActivityThread.main(ActivityThread.java: 5743 )
   at java.lang.reflect.Method.invoke!(Native method)
   at java.lang.reflect.Method.invoke(Method.java: 372 )
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 988 )
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 783 )
......
//省略一些不常關注堆棧打印
......</code>

至此常見的應用開發中ANR分析定位就能夠解決了。

2-4 應用UI性能分析解決總結

能夠看見,關於Android UI卡頓的性能分析仍是有不少工具的,上面只是介紹了應用開發中咱們常用的一些而已,還有一些其餘的,譬如Oprofile等工具不怎麼經常使用,這裏就再也不詳細介紹。

經過上面UI性能的原理、緣由、工具分析總結能夠發現,咱們在開發應用時必定要時刻重視性能問題,如若真的沒留意出現了性能問題,不妨使用上面的一些案例方式進行分析。可是那終歸是補救措施,在咱們知道上面UI卡頓原理以後咱們應該儘可能從項目代碼架構搭建及編寫時就避免一些UI性能問題,具體項目中常見的注意事項以下:

佈局優化;儘可能使用include、merge、ViewStub標籤,儘可能不存在冗餘嵌套及過於複雜佈局(譬如10層就會直接異常),儘可能使用GONE替換INVISIBLE,使用weight後儘可能將width和heigh設置爲0dp減小運算,Item存在很是複雜的嵌套時考慮使用自定義Item View來取代,減小measure與layout次數等。

列表及Adapter優化;儘可能複用getView方法中的相關View,不重複獲取實例致使卡頓,列表儘可能在滑動過程當中不進行UI元素刷新等。

背景和圖片等內存分配優化;儘可能減小沒必要要的背景設置,圖片儘可能壓縮處理顯示,儘可能避免頻繁內存抖動等問題出現。

自定義View等繪圖與佈局優化;儘可能避免在draw、measure、layout中作過於耗時及耗內存操做,尤爲是draw方法中,儘可能減小draw、measure、layout等執行次數。

避免ANR,不要在UI線程中作耗時操做,遵照ANR規避守則,譬如屢次數據庫操做等。

固然了,上面只是列出了咱們項目中常見的一些UI性能注意事項而已,相信還有不少其餘的狀況這裏沒有說到,歡迎補充。還有一點就是咱們上面所謂的UI性能優化分析總結等都是建議性的,由於性能這個問題是一個涉及面很廣很泛的問題,有些優化不是必需的,有些優化是必需的,有些優化掉之後又是得不償失的,因此咱們通常着手解決那些必須的就能夠了。

3 應用開發Memory內存性能分析優化

說完了應用開發中的UI性能問題後咱們就該來關注應用開發中的另外一個重要、嚴重、很是重要的性能問題了,那就是內存性能優化分析。Android其實就是嵌入式設備,嵌入式設備核心關注點之一就是內存資源;有人說如今的設備都在堆硬件配置(譬如國產某米的某兔跑分手機、盒子等),因此內存不會再像之前那麼緊張了,其實這句話聽着沒錯,但爲啥再牛逼配置的Android設備上有些應用仍是越用系統越卡呢?這裏面的緣由有不少,不過相信有了這一章下面的內容分析,做爲一個移動開發者的你就有能力打理好本身應用的那一畝三分地內存了,能作到這樣就足以了。關於Android內存優化,這裏有一篇Google的官方指導文檔,可是本文爲本身項目摸索,會有不少不同的地方。

3-1 Android內存管理原理

系統級內存管理:

Android系統內核是基於Linux,因此說Android的內存管理其實也是Linux的升級版而已。Linux在進程中止後就結束該進程,而Android把這些中止的進程都保留在內存中,直到系統須要更多內存時才選擇性的釋放一些,保留在內存中的進程默認(不包含後臺service與Thread等單獨UI線程的進程)不會影響總體系統的性能(速度與電量等)且當再次啓動這些保留在內存的進程時能夠明顯提升啓動速度,不須要再去加載。

再直白點就是說Android系統級內存管理機制其實相似於Java的垃圾回收機制,這下明白了吧;在Android系統中框架會定義以下幾類進程、在系統內存達到規定的不一樣level閾值時觸發清空不一樣level的進程類型。

這裏寫圖片描述

能夠看見,所謂的咱們的Service在後臺跑着跑着掛了,或者盒子上有些大型遊戲啓動起來就掛(以前我在上家公司作盒子時碰見過),有一個直接的緣由就是這個閾值定義的太大,致使系統一直認爲已經達到閾值,因此進行優先清除了符合類型的進程。因此說,該閾值的設定是有一些講究的,額,扯多了,咱們主要是針對應用層內存分析的,系統級內存回收瞭解這些就基本夠解釋咱們應用在設備上的一些表現特徵了。

應用級內存管理:

在說應用級別內存管理原理時你們先想一個問題,假設有一個內存爲1G的Android設備,上面運行了一個很是很是吃內存的應用,若是沒有任何機制的狀況下是否是用着用着整個設備會由於咱們這個應用把1G內存吃光而後整個系統運行癱瘓呢?

哈哈,其實Google的工程師纔不會這麼傻的把系統設計這麼差勁。爲了使系統不存在咱們上面假想狀況且能安全快速的運行,Android的框架使得每一個應用程序都運行在單獨的進程中(這些應用進程都是由Zygote進程孵化出來的,每一個應用進程都對應本身惟一的虛擬機實例);若是應用在運行時再存在上面假想的狀況,那麼癱瘓的只會是本身的進程,不會直接影響系統運行及其餘進程運行。

既然每一個Android應用程序都執行在本身的虛擬機中,那瞭解Java的必定明白,每一個虛擬機一定會有堆內存閾值限制(值得一提的是這個閾值通常都由廠商依據硬件配置及設備特性本身設定,沒有統一標準,能夠爲64M,也能夠爲128M等;它的配置是在Android的屬性系統的/system/build.prop中配置dalvik.vm.heapsize=128m便可,若存在dalvik.vm.heapstartsize則表示初始申請大小),也即一個應用進程同時存在的對象必須小於閾值規定的內存大小才能夠正常運行。

接着咱們運行的App在本身的虛擬機中內存管理基本就是遵循Java的內存管理機制了,系統在特定的狀況下主動進行垃圾回收。可是要注意的一點就是在Android系統中執行垃圾回收(GC)操做時全部線程(包含UI線程)都必須暫停,等垃圾回收操做完成以後其餘線程才能繼續運行。這些GC垃圾回收通常都會有明顯的log打印出回收類型,常見的以下:

GC_MALLOC——內存分配失敗時觸發;

GC_CONCURRENT——當分配的對象大小超過384K時觸發;

GC_EXPLICIT——對垃圾收集的顯式調用(System.gc()) ;

GC_EXTERNAL_ALLOC——外部內存分配失敗時觸發;

經過上面這幾點的分析能夠發現,應用的內存管理其實就是一個蘿蔔一個坑,坑都通常大,你在開發應用時要保證的是內存使用同一時刻不能超過坑的大小,不然就裝不下了。

3-2 Android內存泄露性能分析

有了關於Android的一些內存認識,接着咱們來看看關於Android應用開發中常出現的一種內存問題—-內存泄露。

3-2-1 Android應用內存泄露概念

衆所周知,在Java中有些對象的生命週期是有限的,當它們完成了特定的邏輯後將會被垃圾回收;可是,若是在對象的生命週期原本該被垃圾回收時這個對象還被別的對象所持有引用,那就會致使內存泄漏;這樣的後果就是隨着咱們的應用被長時間使用,他所佔用的內存愈來愈大。以下就是一個最多見簡單的泄露例子(其它的泄露再也不一一列舉了):

 
1
2
3
4
5
6
7
8
9
10
11
12
<code class = "hljs" java= "" > public final class MainActivity extends Activity {
     private DbManager mDbManager;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
 
         setContentView(R.layout.activity_main);
 
         //DbManager是一個單例模式類,這樣就持有了MainActivity引用,致使泄露
         mDbManager = DbManager.getInstance( this );
     }
}</code>

能夠看見,上面例子中咱們讓一個單例模式的對象持有了當前Activity的強引用,那在當前Acvitivy執行完onDestroy()後,這個Activity就沒法獲得垃圾回收,也就形成了內存泄露。

內存泄露能夠引起不少的問題,常見的內存泄露致使問題以下:

應用卡頓,響應速度慢(內存佔用高時JVM虛擬機會頻繁觸發GC);

應用被從後臺進程幹爲空進程(上面系統內存原理有介紹,也就是超過了閾值);

應用莫名的崩潰(上面應用內存原理有介紹,也就是超過了閾值OOM);

形成內存泄露泄露的最核心原理就是一個對象持有了超過本身生命週期之外的對象強引用致使該對象沒法被正常垃圾回收;能夠發現,應用內存泄露是個至關棘手重要的問題,咱們必須重視。

3-2-2 Android應用內存泄露察覺手段

知道了內存泄露的概念以後確定就是想辦法來確認本身的項目是否存在內存泄露了,那該如何察覺本身項目是否存在內存泄露呢?以下提供了幾種經常使用的方式:

察覺方式 場景
AS的Memory窗口 平時用來直觀瞭解本身應用的全局內存狀況,大的泄露才能有感知。
DDMS-Heap內存監測工具 同上,大的泄露才能有感知。
dumpsys meminfo命令 經常使用方式,能夠很直觀的察覺一些泄露,但不全面且常規足夠用。
leakcanary神器 比較強大,能夠感知泄露且定位泄露;實質是MAT原理,只是更加自動化了,當現有代碼量已經龐大成型,且沒法很快察覺掌控全局代碼時極力推薦;或者是偶現泄露的狀況下極力推薦。

AS的Memory窗口以下,詳細的說明這裏就不解釋了,很簡單很直觀(使用頻率高):

這裏寫圖片描述

DDMS-Heap內存監測工具窗口以下,詳細的說明這裏就不解釋了,很簡單(使用頻率不高):

這裏寫圖片描述

dumpsys meminfo命令以下(使用頻率很是高,很是高效,個人最愛之一,平時通常關注幾個重要的Object個數便可判斷通常的泄露;固然了,adb shell dumpsys meminfo不跟參數直接展現系統全部內存狀態):

這裏寫圖片描述

leakcanary神器使用這裏先不說,下文會專題介紹,你會震撼的一B。有了這些工具的定位咱們就能很方便的察覺咱們App的內存泄露問題,察覺到之後該怎麼定位分析呢,繼續往下看。

3-2-3 Android應用內存泄露leakcanary工具定位分析

leakcanary是一個開源項目,一個內存泄露自動檢測工具,是著名的GitHub開源組織Square貢獻的,它的主要優點就在於自動化過早的發覺內存泄露、配置簡單、抓取貼心,缺點在於還存在一些bug,不過正常使用百分之九十狀況是OK的,其核心原理與MAT工具相似。

關於leakcanary工具的配置使用方式這裏再也不詳細介紹,由於真的很簡單,詳情點我參考官方教程學習使用便可。

PS:以前在優化性能時發現咱們有一個應用有兩個界面退出後Activity沒有被回收(dumpsys meminfo發現一直在加),因此就懷疑可能存在內存泄露。可是問題來了,這兩個Activity的邏輯十分複雜,代碼也不是我寫的,相關聯的代碼量也十分龐大,更加鬱悶的是很難判斷是哪一個版本修改致使的,這時候只知道有泄露,卻沒法定位具體緣由,使用MAT分析解決掉了一個可疑泄露後發現泄露又變成了機率性的。能夠發現,對於這種機率性的泄露用MAT去主動抓取確定是很耗時耗力的,因此決定直接引入leakcanary神器來檢測項目,後來很快就完全解決了項目中全部必現的、偶現的內存泄露。

總之一點,工具再強大也只是幫咱們定位可能的泄露點,而最核心的GC ROOT泄露信息推導出泄露問題及如何解決仍是須要你把住代碼邏輯及泄露核心概念去推理解決。

3-2-4 Android應用內存泄露MAT工具定位分析

Eclipse Memory Analysis Tools(點我下載)是一個專門分析Java堆數據內存引用的工具,咱們能夠使用它方便的定位內存泄露緣由,核心任務就是找到GC ROOT位置便可,哎呀,關於這個工具的使用我是真的不想說了,本身搜索吧,實在簡單、傳統的不行了。

PS:這是開發中使用頻率很是高的一個工具之一,麻煩務必掌握其核心使用技巧,雖然Android Studio已經實現了部分功能,可是真的很難用,遇到問題目前仍是使用Eclipse Memory Analysis Tools吧。

原諒我該小節的放蕩不羈!!!!(其實我是困了,嗚嗚!)

3-2-5 Android應用開發規避內存泄露建議

有了上面的原理及案例處理其實還不夠,由於上面這些處理辦法是補救的措施,咱們正確的作法應該是在開發過程當中就養成良好的習慣和敏銳的嗅覺纔對,因此下面給出一些應用開發中常見的規避內存泄露建議:

Context使用不當形成內存泄露;不要對一個Activity Context保持長生命週期的引用(譬如上面概念部分給出的示例)。儘可能在一切能夠使用應用ApplicationContext代替Context的地方進行替換(原理我前面有一篇關於Context的文章有解釋)。

非靜態內部類的靜態實例容易形成內存泄漏;即一個類中若是你不可以控制它其中內部類的生命週期(譬如Activity中的一些特殊Handler等),則儘可能使用靜態類和弱引用來處理(譬如ViewRoot的實現)。

警戒線程未終止形成的內存泄露;譬如在Activity中關聯了一個生命週期超過Activity的Thread,在退出Activity時切記結束線程。一個典型的例子就是HandlerThread的run方法是一個死循環,它不會本身結束,線程的生命週期超過了Activity生命週期,咱們必須手動在Activity的銷燬方法中中調運thread.getLooper().quit();纔不會泄露。

對象的註冊與反註冊沒有成對出現形成的內存泄露;譬如註冊廣播接收器、註冊觀察者(典型的譬如數據庫的監聽)等。

建立與關閉沒有成對出現形成的泄露;譬如Cursor資源必須手動關閉,WebView必須手動銷燬,流等對象必須手動關閉等。

不要在執行頻率很高的方法或者循環中建立對象,能夠使用HashTable等建立一組對象容器從容器中取那些對象,而不用每次new與釋放。

避免代碼設計模式的錯誤形成內存泄露;譬如循環引用,A持有B,B持有C,C持有A,這樣的設計誰都得不到釋放。

關於規避內存泄露上面我只是列出了我在項目中常常碰見的一些狀況而已,確定不全面,歡迎拍磚!固然了,只有咱們作到好的規避加上強有力的判斷嗅覺泄露才能讓咱們的應用駕馭好本身的一畝三分地。

3-3 Android內存溢出OOM性能分析

上面談論了Android應用開發的內存泄露,下面談談內存溢出(OOM);其實能夠認爲內存溢出與內存泄露是交集關係,具體以下圖:

這裏寫圖片描述

下面咱們就來看看內存溢出(OOM)相關的東東吧。

3-3-1 Android應用內存溢出OOM概念

上面咱們探討了Android內存管理和應用開發中的內存泄露問題,能夠知道內存泄露通常影響就是致使應用卡頓,可是極端的影響是使應用掛掉。前面也提到過應用的內存分配是有一個閾值的,超過閾值就會出問題,這裏咱們就來看看這個問題—–內存溢出(OOM–OutOfMemoryError)。

內存溢出的主要致使緣由有以下幾類:

應用代碼存在內存泄露,長時間積累沒法釋放致使OOM;

應用的某些邏輯操做瘋狂的消耗掉大量內存(譬如加載一張不通過處理的超大超高清圖片等)致使超過閾值OOM;

能夠發現,不管哪一種類型,致使內存溢出(OutOfMemoryError)的核心緣由就是應用的內存超過閾值了。

3-3-2 Android應用內存溢出OOM性能分析

經過上面的OOM概念和那幅交集圖能夠發現,要想分析OOM緣由和避免OOM須要分兩種狀況考慮,泄露致使的OOM,申請過大致使的OOM。

內存泄露致使的OOM分析:

這種OOM一旦發生後會在logcat中打印相關OutOfMemoryError的異常棧信息,不過你別高興太早,這種狀況下致使的OOM打印異常信息是沒有太大做用,由於這種OOM的致使通常都以下圖狀況(圖示爲了說明問題數據和場景有誇張,請忽略):

這裏寫圖片描述

從圖片能夠看見,這種OOM咱們有時也遇到,第一反應是去分析OOM異常打印棧,但是後來發現打印棧打印的地方沒有啥問題,沒有可優化的餘地了,因而就鬱悶了。其實這時候你留心觀察幾個現象便可,以下:

留意你執行觸發OOM操做前的界面是否有卡頓或者比較密集的GC打印; 使用命令查看下當前應用佔用內存狀況;

確認了以上這些現象你基本能夠判定該OOM的log真的沒用,真正致使問題的緣由是內存泄露,因此咱們應該按照上節介紹的方式去着手排查內存泄露問題,解決掉內存泄露後紅色空間都能獲得釋放,再去顯示一張0.8M的優化圖片就不會再報OOM異常了。

不珍惜內存致使的OOM分析:

上面說了內存泄露致使的OOM異常,下面咱們再來看一幅圖(數據和場景描述有誇張,請忽略),以下:

這裏寫圖片描述

可見,這種類型的OOM就很好定位緣由了,通常均可以從OOM後的log中得出分析定位。

以下例子,咱們在Activity中的ImageView放置一張未優化的特大的(30多M)高清圖片,運行直接崩潰以下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<code avrasm= "" class = "language-txt" hljs= "" > //拋出OOM異常
10 - 10 09 : 01 : 04.873 11703 - 11703 /? E/art: Throwing OutOfMemoryError Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM
10 - 10 09 : 01 : 04.940 11703 - 11703 /? E/art: Throwing OutOfMemoryError Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM
//堆棧打印
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime: FATAL EXCEPTION: main
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime: Process: com.example.application, PID: 11703
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.application/com.example.myapplication.MainActivity}: android.view.InflateException: Binary XML file line # 21 : Error inflating class <unknown>
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java: 2610 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java: 2684 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.app.ActivityThread.access$ 800 (ActivityThread.java: 177 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java: 1542 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java: 111 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.os.Looper.loop(Looper.java: 194 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java: 5743 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java: 372 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 988 )
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 783 )
//出錯地點,緣由是21行的ImageView設置的src是一張未優化的31M的高清圖片
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:  Caused by: android.view.InflateException: Binary XML file line # 21 : Error inflating class <unknown>
10 - 10 09 : 01 : 04.958 11703 - 11703 /? E/AndroidRuntime:     at android.view.LayoutInflater.createView(LayoutInflater.java: 633 )</unknown></unknown></code>

經過上面的log能夠很方便的看出來問題緣由所在地,那接下來的作法就是優化唄,下降圖片的相關規格便可(譬如使用BitmapFactory的Option類操做等)。

PS:提醒一句的是記得應用所屬的內存是區分Java堆和native堆的!

3-3-3 Android應用規避內存溢出OOM建議

仍是那句話,等待OOM發生是爲時已晚的事,咱們應該將其扼殺於萌芽之中,至於如何在開發中規避OOM,以下給出一些咱們應用開發中的經常使用的策略建議:

時刻記得不要加載過大的Bitmap對象;譬如對於相似圖片加載咱們要經過BitmapFactory.Options設置圖片的一些採樣比率和複用等,具體作法點我參考官方文檔,不過過咱們通常都用fresco或Glide開源庫進行加載。

優化界面交互過程當中頻繁的內存使用;譬如在列表等操做中只加載可見區域的Bitmap、滑動時不加載、中止滑動後再開始加載。

有些地方避免使用強引用,替換爲弱引用等操做。

避免各類內存泄露的存在致使OOM。

對批量加載等操做進行緩存設計,譬如列表圖片顯示,Adapter的convertView緩存等。

儘量的複用資源;譬如系統自己有不少字符串、顏色、圖片、動畫、樣式以及簡單佈局等資源可供咱們直接使用,咱們本身也要儘可能複用style等資源達到節約內存。

對於有緩存等存在的應用盡可能實現onLowMemory()和onTrimMemory()方法。

儘可能使用線程池替代多線程操做,這樣能夠節約內存及CPU佔用率。

儘可能管理好本身的Service、Thread等後臺的生命週期,不要浪費內存佔用。

儘量的不要使用依賴注入,中看不中用。

儘可能在作一些大內存分配等可疑內存操做時進行try catch操做,避免沒必要要的應用閃退。

儘可能的優化本身的代碼,減小冗餘,進行編譯打包等優化對齊處理,避免類加載時浪費內存。

能夠發現,上面只是列出了咱們開發中常見的致使OOM異常的一些規避原則,還有不少相信尚未列出來,你們能夠自行追加參考便可。

3-4 Android內存性能優化總結

不管是什麼電子設備的開發,內存問題永遠都是一個很深奧、無底洞的話題,上面的這些內存分析建議也單單只是Android應用開發中一些常見的場景而已,真正的達到合理的優化仍是須要不少知識和功底的。

合理的應用架構設計、設計風格選擇、開源Lib選擇、代碼邏輯規範等都會決定到應用的內存性能,咱們必須時刻頭腦清醒的意識到這些問題潛在的風險與優劣,由於內存優化必需要有一個度,不能一味的優化,亦不能置之不理。

4 Android應用API使用及代碼邏輯性能分析

在咱們開發中除過常規的那些經典UI、內存性能問題外其實還存在不少潛在的性能優化、這種優化不是十分明顯,可是在某些場景下倒是很是有必要的,因此咱們簡單列舉一些常見的其餘潛在性能優化技巧,具體以下探討。

4-1 Android應用String/StringBuilder/StringBuffer優化建議

字符串操做在Android應用開發中是十分常見的操做,也就是這個最簡單的字符串操做卻也暗藏不少潛在的性能問題,下面咱們實例來講說。

先看下面這個關於String和StringBuffer的對比例子:

 
1
2
3
4
5
6
7
8
<code class = "hljs" javascript= "" > //性能差的實現
String str1 = Name:;
String str2 = GJRS;
String Str = str1 + str2;
//性能好的實現
String str1 = Name:;
String str2 = GJRS;
StringBuffer str = new StringBuilder().append(str1).append(str2);</code>

經過這個例子能夠看出來,String對象(記得是對象,不是常量)和StringBuffer對象的主要性能區別在於String對象是不可變的,因此每次對String對象作改變操做(譬如「+」操做)時其實都生成了新的String對象實例,因此會致使內存消耗性能問題;而StringBuffer對象作改變操做每次都會對本身進行操做,因此不須要消耗額外的內存空間。

咱們再看一個關於String和StringBuffer的對比例子:

 
1
2
3
4
<code class = "hljs" go= "" > //性能差的實現
StringBuffer str = new StringBuilder().append(Name:).append(GJRS);
//性能好的實現
String Str = Name: + GJRS;</code>

在這種狀況下你會發現StringBuffer的性能反而沒有String的好,緣由是在JVM解釋時認爲
String Str = Name: + GJRS;就是String Str = Name:GJRS;,因此天然比StringBuffer快了。

能夠發現,若是咱們拼接的是字符串常量則String效率比StringBuffer高,若是拼接的是字符串對象,則StringBuffer比String效率高,咱們在開發中要酌情選擇。固然,除過注意StringBuffer和String的效率問題,咱們還應該注意另外一個問題,那就是StringBuffer和StringBuilder的區別,其實StringBuffer和StringBuilder都繼承自同一個父類,只是StringBuffer是線程安全的,也就是說在不考慮多線程狀況下StringBuilder的性能又比StringBuffer高。

PS:若是想追究清楚他們之間具體細節差別,麻煩本身查看實現源碼便可。

4-2 Android應用OnTrimMemory()實現性能建議

OnTrimMemory是Android 4.0以後加入的一個回調方法,做用是通知應用在不一樣的狀況下進行自身的內存釋放,以免被系統直接殺掉,提升應用程序的用戶體驗(冷啓動速度是熱啓動的2~3倍)。系統會根據當前不一樣等級的內存使用狀況調用這個方法,而且傳入當前內存等級,這個等級有不少種,咱們能夠依據狀況實現不一樣的等級,這裏不詳細介紹,可是要說的是咱們應用應該至少實現以下等級:

TRIM_MEMORY_BACKGROUND
內存已經很低了,系統準備開始根據LRU緩存來清理進程。這時候若是咱們手動釋放一些不重要的緩存資源,則當用戶返回咱們應用時會感受到很順暢,而不是從新啓動應用。

能夠實現OnTrimMemory方法的系統組件有Application、Activity、Fragement、
Service、ContentProvider;關於OnTrimMemory釋放哪些內存其實在架構階段就要考慮清楚哪些對象是要常駐內存的,哪些是伴隨組件週期存在的,通常須要釋放的都是緩存。
以下給出一個咱們項目中經常使用的例子:

 
1
2
3
4
5
6
<code class = "hljs" java= "" > @Override
public void onTrimMemory( int level) {
    if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
        clearCache();
    }
}</code>

一般在咱們代碼實現了onTrimMemory後很難復顯這種內存消耗場景,可是你又怕引入新Bug,想一想辦法測試。好在咱們有一個快捷的方式來模擬觸發該水平內存釋放,以下命令:

 
1
<code class = "hljs" livecodeserver= "" >adb shell dumpsys gfxinfo packagename -cmd trim value</code>

packagename爲包名或者進程id,value爲ComponentCallbacks2.java裏面定義的值,能夠爲80、60、40、20、5等,咱們模擬觸發其中的等級便可。

4-3 Android應用HashMap與ArrayMap及SparseArray優化建議

在Android開發中涉及到數據邏輯部分大部分用的都是Java的API(譬如HashMap),可是對於Android設備來講有些Java的API並不適合,可能會致使系統性能降低,好在Google團隊已經意識到這些問題,因此他們針對Android設備對Java的一些API進行了優化,優化最多就是使用了ArrayMap及SparseArray替代HashMap來得到性能提高。

HashMap:

HashMap內部使用一個默認容量爲16的數組來存儲數據,數組中每個元素存放一個鏈表的頭結點,其實整個HashMap內部結構就是一個哈希表的拉鍊結構。HashMap默認實現的擴容是以2倍增長,且獲取一個節點採用了遍歷法,因此相對來講不管從內存消耗仍是節點查找上都是十分昂貴的。

SparseArray:

SparseArray比HashMap省內存是由於它避免了對Key進行自動裝箱(int轉Integer),它內部是用兩個數組來進行數據存儲的(一個存Key,一個存Value),它內部對數據採用了壓縮方式來表示稀疏數組數據,從而節約內存空間,並且其查找節點的實現採用了二分法,很明顯能夠看見性能的提高。

ArrayMap:

ArrayMap內部使用兩個數組進行數據存儲,一個記錄Key的Hash值,一個記錄Value值,它和SparseArray相似,也會在查找時對Key採用二分法。

有了上面的基本瞭解咱們能夠得出結論供開發時參考,當數據量不大(千位級內)且Key爲int類型時使用SparseArray替換HashMap效率高;當數據量不大(千位級內)且數據類型爲Map類型時使用ArrayMap替換HashMap效率高;其餘狀況下HashMap效率相對高於兩者。

4-4 Android應用ContentProviderOperation優化建議

ContentProvider是Android應用開發的核心組件之一,有時候在開發中須要使用ContentProvider對多行數據進行操做,咱們的作法通常是屢次調運相關操做方法,卻不知這種實現方式是很是低性能的,取而代之的作法應該是使用批量操做,具體爲了使批量更新、插入、刪除數據操做更加方便官方提供了ContentProviderOperation工具類。因此在咱們開發中遇到相似情景時請務必使用批量操做,具體的優點以下:

全部的操做都在一個事務中執行,能夠保證數據的完整性。

批量操做在一個事務中執行,因此只用打開、關閉一個事務。

減輕應用程序與ContentProvider間的屢次頻繁交互,提高性能。

能夠看見,這對於數據庫操做來講是一個很是有用的優化措施,煩請務必重視(咱們項目優化過,的確有很大提高)。

4-5 Android應用其餘邏輯優化建議

關於API及邏輯性能優化其實有多知識點的,這裏沒法一一列出,只能給出一些重要的知識點,下面再給出一些常見的優化建議:

避免在Android中使用Java的枚舉類型,由於編譯後不但佔空間,加載也費時,徹底沒有static final的變量好用、高效。

Handler發送消息時儘可能使用obtain去獲取已經存在的Message對象進行復用,而不是新new Message對象,這樣能夠減輕內存壓力。

在使用後臺Service時儘可能將可以替換爲IntentService的地方替換爲此,這樣能夠減輕系統壓力、省電、省內存、省CPU佔用率。

在當前類內部儘可能不要經過本身的getXXX、setXXX對本身內部成員進行操做,而是直接使用,這樣能夠提升代碼執行效率。

不要一味的爲了設計模式而過度的抽象代碼,由於代碼抽象係數與代碼加載執行時間成正比。

儘可能減小鎖個數、減少鎖範圍,避免形成性能問題。

合理的選擇使用for循環與加強型for循環,譬如不要在ArrayList上使用加強型for循環等。

哎呀,相似的小優化技巧有不少,這裏不一一列舉了,自行發揮留意便可。

5 Android應用移動設備電池耗電性能分析

有了UI性能優化、內存性能優化、代碼編寫優化以後咱們在來講說應用開發中很重要的一個優化模塊—–電量優化。

5-1 Android應用耗電量概念

在盒子等開發時可能電量優化不是特別重視(視盒子待機真假待機模式而定),可是在移動設備開發中耗電量是一個很是重要的指標,若是用戶一旦發現咱們的應用很是耗電,很差意思,他們大多會選擇卸載來解決此類問題,因此耗電量是一個十分重要的問題。

關於咱們應用的耗電量狀況咱們能夠進行定長時間測試,至於具體的耗電量統計等請參考此文,同時咱們還能夠直接經過Battery Historian Tool來查看詳細的應用電量消耗狀況。最簡單經常使用辦法是經過命令直接查看,以下:

 
1
<code class = "hljs" livecodeserver= "" >adb shell dumpsys batterystats</code>

其實咱們一款應用耗電量最大的部分不是UI繪製顯示等,常見耗電量最大緣由基本都是由於網絡數據交互、GPS定位、大量內存性能問題、冗餘的後臺線程和Service等形成。

5-2 Android應用耗電量優化建議

優化電量使用狀況咱們不只能夠使用系統提供的一些API去處理,還能夠在平時編寫代碼時就養成好的習慣。具體的一些建議以下:

在須要網絡的應用中,執行某些操做前儘可能先進行網絡狀態判斷。

在網絡應用傳輸中使用高效率的數據格式和解析方法,譬如JSON等。

在傳輸用戶反饋或者下載OTA升級包等不是十分緊急的操做時儘可能採用壓縮數據進行傳輸且延遲到設備充電和WIFI狀態時進行。

在有必要的狀況下儘可能經過PowerManager.WakeLock和JobScheduler來控制一些邏輯操做達到省電優化。

對定位要求不過高的場景儘可能使用網絡定位,而不是GPS定位。

對於定時任務儘可能使用AlarmManager,而不是sleep或者Timer進行管理。

儘量的減小網絡請求次數和減少網絡請求時間間隔。

後臺任務要儘量少的喚醒CPU,譬如IM通訊的長鏈接心跳時間間隔、一些應用的後臺定時喚醒時間間隔等要設計合理。

特殊耗電業務狀況能夠進行彈窗等友好的交互設計提醒用戶該操做會耗用過多電量。

能夠看見,上面只是一些常見的電量消耗優化建議。總之,做爲應用開發者的咱們要意識到電量損耗對於用戶來講是很是敏感的,只有咱們作到合理的電量優化才能贏得用戶的芳心。

6 Android應用開發性能優化總結

性能優化是一個很大的話題,上面咱們談到的只是應用開發中常見的性能問題,也是應用開發中性能問題的冰山一角,更多的性能優化技巧和能力不是靠看出來,而是靠經驗和實戰結果總結出來的,因此說性能優化是一個涉及面很是廣的話題,若是你想對你的應用進行性能你必須對你應用的整個框架有一個很是清晰的認識。

固然了,若是在咱們開發中只是一味的追求各類極致的優化也是不對的。由於優化原本就是存在風險的,甚至有些過分的優化會直接致使項目的臃腫,因此不要由於極致的性能優化而破壞掉了你項目的合理架構。

總之一句話,性能優化適可而止,請酌情優化。

【工匠若水 http://blog.csdn.net/yanbober 轉載請註明出處】

相關文章
相關標籤/搜索