Android APP性能分析方法及工具

近期讀到《Speed up your app》一文。這是一篇關於Android APP性能分析、優化的文章。在這篇文章中,做者介紹他的APP分析優化規則、使用的工具和方法。我以爲值得你們借鑑。英文好的讀者可讀原文(連接:http://blog.udinic.com/2015/09/15/speed-up-your-app)。html

一、做者的規則

做者每次着手處理或尋找性能問題時,遵循下列規則:java

  • 時常檢測

在更新APP先後,用測試工具軟件多檢測幾回APP性能,可快速獲得測試數據。這些數字是不會說謊的。而僅僅用眼睛觀察APP性能是確定不行的。如你在觀察幾回相同的動畫效果後,你就想象它運行的夠快了,從而忽略一下問題。android

  • 使用低端設備

現在硬件性能在不斷的提高,若是僅在最新設備運行APP,可能不能充分暴露出APP中存在的性能問題。另外,儘管用戶設備換手率已經很高了,但仍然不是全部用戶都是使用最新的和最強功能的設備。因此應該使用低端設備上,運行APP,這能夠幫助你更有效地發現問題。git

  • 權衡

性能優化是要綜合各方面因素進行評判、權衡。由於優化一項性能多是要以犧牲另外一項性能爲代價的。分析、查找、修復是要花費不少時間。你要準備好自我犧牲。github

二、做者的分析方法及使用工具

做者採用自頂向下方法,從手機運行的概況開始,逐級深刻分析:方法的性能、內存使用狀況、GPU渲染效果、視圖層次、圖形疊加繪製、圖像透明值;解釋Honeycomb引入的硬件加速以及視圖層。緩存

2.1 Systrace

手機實際就是一臺功能強大的計算機,同時間作不少事情。Systrace可以展現手機運行的概況。安全

做者在Systrace中選取一個Alert,作爲例子,講解分析發現問題的方法:性能優化

1)由Alert找到函數(如:long View#draw()),再展開「Inflation during ListView recycling」數據結構

2)能夠查看到函數的耗時,更詳細觀察分析其中哪項花費時間較長。架構

3)選擇一幀查看它花費多久時間。若有一幀繪製用時超過19ms。展開「Scheduling delay」

4)它的值(Wall duration和CPU duration之間的差別)代表有很長時間CPU沒有安排這個線程。

這就須要查查在整個這段時間裏CPU都作了什麼?可是Systrace只能查看運行概況,還不能獲得更深層次的分析。爲了找到CPU運行繁忙的真正緣由,做者使用另外一個工具:Traceview。

2.2 Traceview

Traceview是性能分析工具,能夠顯示每一個方法運行時間。可從Android Device Monitor中啓動,也可從代碼中啓動。

做者以「滾屏」動做爲例說明Traceview分析方法。在「滾屏」動做的跟蹤記錄中,找到getView()方法,發現它被調用12次,每次CPU用時約3ms。可是每次完成整個調用卻用時162ms!這就是個問題!

做者就繼續查看getView()的子方法,查看各個子方法所用時間在總時間中比例。他發現Thread.join()的Inclusive Real Time佔用約98%。他順藤摸瓜,找啓動子方法的Thread.run()方法(它是建立一個新線程時所調用的方法),逐個跟着,直到找到「元兇」:BgService.doWork()方法。

另外,GC(Garbage Collector – 垃圾收集器)不按期運行清理不用的對象。GC的頻繁運行也會使得APP運行慢下來。爲此,做者的下一步就是針對內存進行分析。

2.3 內存分析(Memory Profiling)

Android Studio逐步在改善,有愈來愈多的工具能夠幫助咱們找到和分析性能問題。做者用其分析內存使用狀況。

2.3.1 Heap dump

Heap Dump能夠看到Heap中依據類名排序實例的直方圖。每一個都有分配對象的總數,實例的大小和留在內存中對象的大小。後者告訴咱們這些實例釋放後,可以釋放多少內存。這可幫助咱們識別出大的數據結構和對象關係。這些信息能夠幫助咱們構建更有效的數據結構,解開對象之間聯繫以減小內存駐留,最終儘量的減小內存佔用。

在分析中,可發現「內存泄漏」。解決方法就是要記得在活動即將被銷燬時調用onDestory()方法刪除引用。

內存泄漏和較大對象的heap空間佔用,使得有效內存減小,頻繁引起GC事件,試圖釋放更多的heap空間。這些GC事件佔用CPU,下降了APP性能。若是有效的內存數量不足與知足APP,且heap空間不能在擴大,就引起 —— OutOfMemortException —— APP崩潰。

Eclipse MAT(Eclipse Memory Analyze Tool)也是不錯的內存分析工具。

2.3.2 Allocation Tracker

Allocation Tracker可生成在跟蹤期間內存分配給全部實例的狀況報告,報告可按類分組或按方法分組。它以很好的可視化方式展現哪一個實例所得到內存最多。

使用這些信息,能夠找出分配大內存的方法,和可能頻繁觸發GC的事件。

2.3.3 General memory tips

做者給出一些技巧:

  • 枚舉

一直是討論性能的熱門話題。枚舉比普一般數佔用更多的內存空間嗎?是的。但這確定是壞事嗎?未必。如在編寫代碼庫,須要強類型安全性,這就應該使用它。若是有一組能夠歸結在一塊兒的常數,此時使用枚舉也許不不合適。怎樣決定,你須要權衡考慮。

  • Auto-boxing

是自動將原始數據類型轉換爲其對應的對象表示(如:int 到 Integer)。每次原始數據類型被「裝箱」到對象,就會產生一個新的對象(我知道這使人震驚)。若是有許多這樣的操做,那麼GC就頻繁地運行。因爲將原始類型數據賦值到對象時,自動進行auto-boxing的,就很容易忽視它的數量。解決方案就是儘可能使數據類型保持一致。若是要在整個APP中使用原始數據類型,就儘可能避免在沒有實際須要時進行Auto-boxing。使用內存分析工具能夠找到許多對象是表示原始數據類型。也能夠用Traceview尋找到Integer.valueOf(),Long.valueOf()等等。

  • HashMap與ArrayMap / Sparse*Array

在Auto-boxing相關問題中,使用HashMap時,就要求用對象做爲鍵值。若是在APP中用原始int類型,那麼在使用HashMap時就須要將int轉化到Integer。這種狀況也許就須要用SparesIntArray。若是在鍵值仍然須要對象類型的狀況下,也能夠改用ArrayMap類。它很是相似HashMap,只是在其內部工做方式不一樣,是以下降速度爲代價,使用較少的內存。這二者佔用內存都比HashMap小,但檢索所花費的時間略高於HashMap。若是數據項少於1000,它們的運行時沒有什麼差異。

  • Context Awareness

Activity內存比較容易泄漏。因爲它們保持UI的全部視圖層次,佔用大量的空間,因此它們的泄漏也是很是「昂貴的」。許多操做都要求Context對象,你發起Activity。假如引用被緩存,而且該對象的存活期要長於你的Activity,若是沒有清理它的引用,你就產生了內存泄漏。

  • 避免非靜態內部類(inner class)

建立一個非靜態內部類,並實例化它,就建立對外部類的隱式引用。若是內部類實例須要的時間比外部類長,這外部類就要在內存中保留,即便它再也不須要了。例如,在Activity類內部,建立一個擴展AsyncTask的非靜態類,而後着手啓動異步任務,在它運行時,銷燬活動。該異步任務在其運行期間,都保持這一Activity運行。解決方案就是不要這樣作。若是須要這樣,就聲明一個靜態內部類。

2.4 GPU Profiling

Android Studio 1.4增長一項新功能:分析GPU渲染功能。做者詳細講解這一新功能的分析方法。

在GPU選項卡下,能夠在屏幕上看到圖形化顯示的渲染每幀所花費的時間。圖形中每條都表示被渲染的一幀。顏色表示進程的不一樣週期:

  • 繪畫(藍色)

表示View#onDraw()方法。那部分創建/更改DisplayList對象,而後轉換成GPU可以理解的OpenGL命令。高的條形多是視圖複雜,而要求更多的時間繪製它們的顯示列表,而許多視圖在短期內就失效了。

  • 準備(紫色)

在Lollipop中,加入另外一個線程,以幫助UI線程渲染更快。這個線程叫:RenderThread。它的責任是轉換顯示列表爲OpenGL命令,再發送給GPU。這樣在渲染過程當中,UI線程能夠開始處理下一個幀。這時UI線程將全部資源傳送給RenderThread。若是有許多資源要傳遞(如許多/繁重顯示列表),這一步可能須要較長時間。

  • 處理(紅色)

執行顯示列表產生OpenGL命令。因爲須要視圖重繪,若是有許多/複雜顯示列表要執行轉換,這一步可能須要較長時間。當視圖無效或是移動時,都要要重繪視圖。

  • 執行(黃色)

發送OpenGL命令到GPU。因爲CPU發送這些緩存的命令到GPU,並期待收回乾淨緩存,這就阻塞調用了。緩存數量有限,而且GPU也很忙 —— CPU會發現本身必須先等待緩存釋放。所以,若是在這一步咱們見高的條形,就可能意味着GPU在繪製UI時很是忙,這個繪製在短期內太複雜了。

具體操做實例見原文。

2.5 Hierarchy Viewer

做者喜好這個工具。他對許多開發者根本不使用這工具感到失望。

使用Hierarchy Viewer,能夠完整地觀察到屏幕視圖層次和全部視圖的屬性。還能夠導出主題(theme)數據,查看到每一個樣式的全部屬性。可是,這只是在Hierarchy Viewer獨立運行時,才能查看這些數據。而不能夠從Android監控器中查看。

做者在設計佈局和優化佈局時使用這個工具。

做者認爲有時間,能夠對每張視圖都測量以及它的全部子視圖。顏色表示視圖與樹中其餘視圖的比較狀況,很容易找出最薄弱的環節。因爲咱們能夠預覽視圖,這樣就可經過視圖樹,跟蹤找出可刪除的冗餘步驟。這其中,對性能影響最大的,被稱爲Overdraw。

2.6 Overdraw

若是GPU須要在屏幕上繪製不少內容,繪製每幀都須要增長時間,這樣執行週期就拉長了,在圖形中以黃色表示。在一些圖形上再疊加繪製,如在紅色背景上繪製黃色按鈕,這就發生Overdraw。這種狀況下,GPU須要先繪製紅色背景,再在其上繪製黃色按鈕,Overdraw就不可避免了。若是有太多的Overdraw層,這就使得GPU超負荷運行,偏離16ms的目標。

設置「Debug GPU Overdraw」開發者選項,全部Overdraw的嚴重程度都以顏色表示出來。1~2倍的Overdraw算好的,甚至有些小的紅色區也不壞。可是若是在屏幕上有許多紅色,這就有問題了。可是都被紅色覆蓋。這就是問題了。做者建議這時僅用一種顏色設置背景來解決這個問題。

注意:默認主題聲明一個全屏窗口背景顏色。若是有不透明佈局的Activity覆蓋在整個屏幕上,能夠經過刪除窗口的背景色消除這層Overdraw。

Hierarchy Viewer可以輸出全部層次到PSD文件中,用Photoshop中打開。在Photoshop中研究不一樣的層就可展現佈局中的全部Overdraw。刪除冗餘的Overdraw,努力性能提升到藍色上。

2.7 Alpha

使用透明效果也會影響性能。爲何?

ImageView相互重疊。用setAlpha()設置alpha值,這將傳遞給全部的子視圖,對幀緩衝區進行繪製。結果都重疊混在一塊兒。幸虧,OS有這個問題的解決方案。佈局數據被複制到off-screen緩衝區,用alpha值對off-screen緩衝區進行處理後,再複製到幀緩衝區中。效果就行了些。可是,咱們爲此付出了代價:把「幀」緩衝區改成off-screen緩衝區,實際上增長了一個隱含的Overdraw層。OS就不知道處理了,因此默認狀況下常常要進行復雜地操做。

不過仍是有方法設置alpha值,避免off-screen緩衝區增長的複雜性:

  • TextView

用setTextColor()替代setAlpha()。使用文本顏色的alpha通道,就可直接用它來繪製的文本。

  • ImageView

用setImageAlpha()替代setAlpha()。理由同TextView。

  • Custom View

若是自定義視圖不支持覆蓋視圖,這複雜行爲是可有可無的。可經過重寫hasOverlappingRendering()方法,讓其返回false,通知OS直接繪製自定義的視圖。還能夠經過重寫onSetAlpha()方面,讓其返回true,選擇手動處理設置,各alpha值對應的操做。

2.8 Hardware Acceleration

在Honeycomb(蜂巢,Android 3.x)引入硬件加速後,在屏幕上渲染APP能夠以新的繪製模型(http://developer.android.com/guide/topics/graphics/hardware-accel.html)進行。新模型引入DisplayList結構,記錄視圖渲染繪製命令。還有另外一個很好的特性,時常被開發人員忽視或不正確地使用 — 視圖層。

使用視圖層,咱們可以非屏幕緩衝區(如前面所見,應用alpha通道)渲染視圖,而且能按照咱們的意願操控它。因爲利用這一特性可以更快地繪製複雜動畫視圖,因此它主要用於動畫。沒有這些層次,在改變更畫屬性(如:x座標、縮放、alpha值等等)後,動畫視圖將無效。對於複雜視圖,這個無效效果都傳遞到全部子視圖,且重繪的成本很高。在硬件支持下,使用視圖層時,GPU會爲視圖建立紋理。有幾個操做能夠用於紋理,而不會破壞它,如:X / Y位置、旋轉、alpha等等。全部都意味着在動畫期間,能夠在屏幕上繪製複雜動畫視圖,而徹底不會破壞它。這使得動畫更加順暢。

提出在使用硬件層時須要記住幾件事:

  • 清理視圖。硬件層消耗有限存儲元件的空間、GPU。因此僅在確實須要的時候使用(像動畫),並在過後清理。
  • 若是在使用硬件層後,改變視圖,硬件層無效,並在非屏幕緩衝區重繪視圖。這會發生在改變那些無硬件層優化的屬性上(迄今爲止,僅優化:旋轉、縮放、x/y、轉換、軸移和alpha)。

三、其餘資料

做者爲說明性能分析、優化,準備不少代碼來模擬情景。這些代碼能夠在Github代碼庫(https://github.com/Udinic/PerformanceDemo) 或是 Google Play(https://play.google.com/store/apps/details?id=com.udinic.perfdemo) 找到。他將不一樣的場景分別放到不一樣的Activity中,並它們編寫文檔,儘量幫助理解使用這些Activity會遇到哪方面的問題。能夠用工具和運行APP來閱讀Activity的javadoc。

 

做者還推薦一些學習交流方法:

  1. 做者極力推薦你們觀看YouTube上一組Android Performance Patterns視頻(https://www.youtube.com/playlist?list=PLOU2XLYxmsIKEOXh5TwZEv89aofHzNCiu),其中許多短視頻來自Google,講解不一樣性能主題。
  2. 加入Android Performance Patterns Google+ community(https://plus.google.com/communities/116342551728637785407),這裏能夠與包括Google人在內的其餘人一塊兒討論性能,分享想法、文章,和提出問題。
  3. 更有趣的連接:
    • 學習Android圖形架構(http://source.android.com/devices/graphics/architecture.html)是怎樣工做的。這裏有你須要知道的Android怎樣渲染UI的一切,講解不一樣的系統元素,如:SurfaceFlinger,以及它們之間怎樣相互交互的。這篇文檔很長,可是值得一讀。
    • 關於Google IO 2012談話(https://www.youtube.com/watch?v=Q8m9sHdyXnE),展現繪圖模型的工做原理,和如何/爲何在UI渲染時會有一個Jank。
    • Android性能研討會(https://www.parleys.com/tutorial/part-2-android-performance-workshop), 從Devoxx 2013開始,展現Android 4.4中繪圖模型的一些優化,演示不一樣的性能優化工具(Systrace,Overdraw等等)。
    • 關於預防性優化(https://medium.com/google-developers/the-truth-about-preventative-optimizations-ccebadfd3eb5)的長篇文章,說明這爲何不一樣於提前(premature)優化。因爲許多開發者認爲影響不明顯,因此沒有優化他們的部分代碼。要記住一點,聚沙成塔就是一個大問題了。若是你有機會優化一小部分代碼,那怕它可能微乎其微,也不要放棄優化。
    • Android的內存管理(https://www.youtube.com/watch?v=_CruQY55HOk)這是Google IO 2011的舊視頻。它仍然頗有意義。它展現了Android怎樣管理APP的內存的,以及怎樣用工具(如Eclipse MAT)找出問題。
    • Google工程師Romain Guy所作的案例分析(http://www.curious-creature.com/docs/android-performance-case-study-1.html),怎樣優化Twitter客戶端。在這個案例分析中,Romain展現了他怎樣在APP中找到性能問題,以及他建議如何修改它們。在一篇回帖中,展現了在從新設計同一APP後產生的其餘問題。

做者但願你們已得到足夠資料和更強的自信。從今天開始優化本身的APP!

 

做者關於性能優化的演講視頻在這裏:http://www.youtube.com/embed/v3DlGOQAIbw?color=white&theme=light

相關文章
相關標籤/搜索