近期讀到《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引入的硬件加速以及視圖層。緩存
手機實際就是一臺功能強大的計算機,同時間作不少事情。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。
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運行慢下來。爲此,做者的下一步就是針對內存進行分析。
Android Studio逐步在改善,有愈來愈多的工具能夠幫助咱們找到和分析性能問題。做者用其分析內存使用狀況。
Heap Dump能夠看到Heap中依據類名排序實例的直方圖。每一個都有分配對象的總數,實例的大小和留在內存中對象的大小。後者告訴咱們這些實例釋放後,可以釋放多少內存。這可幫助咱們識別出大的數據結構和對象關係。這些信息能夠幫助咱們構建更有效的數據結構,解開對象之間聯繫以減小內存駐留,最終儘量的減小內存佔用。
在分析中,可發現「內存泄漏」。解決方法就是要記得在活動即將被銷燬時調用onDestory()方法刪除引用。
內存泄漏和較大對象的heap空間佔用,使得有效內存減小,頻繁引起GC事件,試圖釋放更多的heap空間。這些GC事件佔用CPU,下降了APP性能。若是有效的內存數量不足與知足APP,且heap空間不能在擴大,就引起 —— OutOfMemortException —— APP崩潰。
Eclipse MAT(Eclipse Memory Analyze Tool)也是不錯的內存分析工具。
Allocation Tracker可生成在跟蹤期間內存分配給全部實例的狀況報告,報告可按類分組或按方法分組。它以很好的可視化方式展現哪一個實例所得到內存最多。
使用這些信息,能夠找出分配大內存的方法,和可能頻繁觸發GC的事件。
做者給出一些技巧:
一直是討論性能的熱門話題。枚舉比普一般數佔用更多的內存空間嗎?是的。但這確定是壞事嗎?未必。如在編寫代碼庫,須要強類型安全性,這就應該使用它。若是有一組能夠歸結在一塊兒的常數,此時使用枚舉也許不不合適。怎樣決定,你須要權衡考慮。
是自動將原始數據類型轉換爲其對應的對象表示(如:int 到 Integer)。每次原始數據類型被「裝箱」到對象,就會產生一個新的對象(我知道這使人震驚)。若是有許多這樣的操做,那麼GC就頻繁地運行。因爲將原始類型數據賦值到對象時,自動進行auto-boxing的,就很容易忽視它的數量。解決方案就是儘可能使數據類型保持一致。若是要在整個APP中使用原始數據類型,就儘可能避免在沒有實際須要時進行Auto-boxing。使用內存分析工具能夠找到許多對象是表示原始數據類型。也能夠用Traceview尋找到Integer.valueOf(),Long.valueOf()等等。
在Auto-boxing相關問題中,使用HashMap時,就要求用對象做爲鍵值。若是在APP中用原始int類型,那麼在使用HashMap時就須要將int轉化到Integer。這種狀況也許就須要用SparesIntArray。若是在鍵值仍然須要對象類型的狀況下,也能夠改用ArrayMap類。它很是相似HashMap,只是在其內部工做方式不一樣,是以下降速度爲代價,使用較少的內存。這二者佔用內存都比HashMap小,但檢索所花費的時間略高於HashMap。若是數據項少於1000,它們的運行時沒有什麼差異。
Activity內存比較容易泄漏。因爲它們保持UI的全部視圖層次,佔用大量的空間,因此它們的泄漏也是很是「昂貴的」。許多操做都要求Context對象,你發起Activity。假如引用被緩存,而且該對象的存活期要長於你的Activity,若是沒有清理它的引用,你就產生了內存泄漏。
建立一個非靜態內部類,並實例化它,就建立對外部類的隱式引用。若是內部類實例須要的時間比外部類長,這外部類就要在內存中保留,即便它再也不須要了。例如,在Activity類內部,建立一個擴展AsyncTask的非靜態類,而後着手啓動異步任務,在它運行時,銷燬活動。該異步任務在其運行期間,都保持這一Activity運行。解決方案就是不要這樣作。若是須要這樣,就聲明一個靜態內部類。
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時很是忙,這個繪製在短期內太複雜了。
具體操做實例見原文。
做者喜好這個工具。他對許多開發者根本不使用這工具感到失望。
使用Hierarchy Viewer,能夠完整地觀察到屏幕視圖層次和全部視圖的屬性。還能夠導出主題(theme)數據,查看到每一個樣式的全部屬性。可是,這只是在Hierarchy Viewer獨立運行時,才能查看這些數據。而不能夠從Android監控器中查看。
做者在設計佈局和優化佈局時使用這個工具。
做者認爲有時間,能夠對每張視圖都測量以及它的全部子視圖。顏色表示視圖與樹中其餘視圖的比較狀況,很容易找出最薄弱的環節。因爲咱們能夠預覽視圖,這樣就可經過視圖樹,跟蹤找出可刪除的冗餘步驟。這其中,對性能影響最大的,被稱爲Overdraw。
若是GPU須要在屏幕上繪製不少內容,繪製每幀都須要增長時間,這樣執行週期就拉長了,在圖形中以黃色表示。在一些圖形上再疊加繪製,如在紅色背景上繪製黃色按鈕,這就發生Overdraw。這種狀況下,GPU須要先繪製紅色背景,再在其上繪製黃色按鈕,Overdraw就不可避免了。若是有太多的Overdraw層,這就使得GPU超負荷運行,偏離16ms的目標。
設置「Debug GPU Overdraw」開發者選項,全部Overdraw的嚴重程度都以顏色表示出來。1~2倍的Overdraw算好的,甚至有些小的紅色區也不壞。可是若是在屏幕上有許多紅色,這就有問題了。可是都被紅色覆蓋。這就是問題了。做者建議這時僅用一種顏色設置背景來解決這個問題。
注意:默認主題聲明一個全屏窗口背景顏色。若是有不透明佈局的Activity覆蓋在整個屏幕上,能夠經過刪除窗口的背景色消除這層Overdraw。
Hierarchy Viewer可以輸出全部層次到PSD文件中,用Photoshop中打開。在Photoshop中研究不一樣的層就可展現佈局中的全部Overdraw。刪除冗餘的Overdraw,努力性能提升到藍色上。
使用透明效果也會影響性能。爲何?
ImageView相互重疊。用setAlpha()設置alpha值,這將傳遞給全部的子視圖,對幀緩衝區進行繪製。結果都重疊混在一塊兒。幸虧,OS有這個問題的解決方案。佈局數據被複制到off-screen緩衝區,用alpha值對off-screen緩衝區進行處理後,再複製到幀緩衝區中。效果就行了些。可是,咱們爲此付出了代價:把「幀」緩衝區改成off-screen緩衝區,實際上增長了一個隱含的Overdraw層。OS就不知道處理了,因此默認狀況下常常要進行復雜地操做。
不過仍是有方法設置alpha值,避免off-screen緩衝區增長的複雜性:
用setTextColor()替代setAlpha()。使用文本顏色的alpha通道,就可直接用它來繪製的文本。
用setImageAlpha()替代setAlpha()。理由同TextView。
若是自定義視圖不支持覆蓋視圖,這複雜行爲是可有可無的。可經過重寫hasOverlappingRendering()方法,讓其返回false,通知OS直接繪製自定義的視圖。還能夠經過重寫onSetAlpha()方面,讓其返回true,選擇手動處理設置,各alpha值對應的操做。
在Honeycomb(蜂巢,Android 3.x)引入硬件加速後,在屏幕上渲染APP能夠以新的繪製模型(http://developer.android.com/guide/topics/graphics/hardware-accel.html)進行。新模型引入DisplayList結構,記錄視圖渲染繪製命令。還有另外一個很好的特性,時常被開發人員忽視或不正確地使用 — 視圖層。
使用視圖層,咱們可以非屏幕緩衝區(如前面所見,應用alpha通道)渲染視圖,而且能按照咱們的意願操控它。因爲利用這一特性可以更快地繪製複雜動畫視圖,因此它主要用於動畫。沒有這些層次,在改變更畫屬性(如:x座標、縮放、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。
做者還推薦一些學習交流方法:
做者但願你們已得到足夠資料和更強的自信。從今天開始優化本身的APP!
做者關於性能優化的演講視頻在這裏:http://www.youtube.com/embed/v3DlGOQAIbw?color=white&theme=light