1. 程式人生 > >Android APP效能分析工具

Android APP效能分析工具

本文基本翻譯自Facebook工程師的文章
Speed up your app,也加入了自己的一些內容。

會介紹以下幾個主題

  • Systrace
  • Traceview
  • Memory Profiling
  • Allocation Tracker
  • GPU Profiling
  • Hierarchy Viewer
  • Overdraw
  • Alpha
  • Hardware Acceleration
Systrace

Systrace的功能可以在AS的DDMS中找到,但不太穩定,所以這裡只介紹命令列模式。
另外Systrace只能分析概況,不能定位問題位置,不太感興趣的朋友也可以直接跳到第二節Traceview。
在終端(我的環境是MAC)中先進入sdk/platform-tools/systrace目錄
cd /Users/apple/Documents/Android/sdk/platform-tools/systrace

然後執行Systrace的命令是
python systrace.py --time=10 -a com.duotin.fm -o mynewtrace.html sched gfx view wm
–time=10 表示記錄10秒
更多引數說明請檢視官網
執行完開啟生成的mynewtrace.html檔案
呈現這樣的介面

這裡寫圖片描述

點選第一列的三角形警示圖示或者第二列的圓形警示圖示,都可以檢視警示詳情,點選效果分別如下

這裡寫圖片描述

三角形警示圖示,每個圖示代表一個警示,點選後檢視警示詳情。

這裡寫圖片描述

圓形警示圖示,每個圖示代表一個frame,顯示紅色或者黃色表示此frame的時間已經超過16.6 millisecond per frame的標準,會導致介面失幀。點選後檢視這個frame所有的警示。
此時按”M”快捷鍵可以高亮當前選中的frame。“A”和”D”分別為左移和右移檢視,”W”放大,”S”縮小。
此外,點選各顏色塊,可以檢視各顏色塊的詳情。
點選上圖中的第一個Alert: Inflation during ListView recycling
顯示詳情

這裡寫圖片描述

可以看出Inflation during ListView recycling的執行時間是32ms(遠遠超過了16ms的限制),共5個item,平均到每個item為6ms。通過點選該frame範圍內的顏色塊,可以檢視各個方法的詳情。

我們再點選一個圓形警示圖示並高亮

這裡寫圖片描述

頂部顯示此frame耗時19ms, 點選右下方的Alert,顯示有 Scheduled delay。
Scheduled delay 是指告訴CPU執行任務,但CPU太忙了,任務被延遲執行了。
點選下面的一個顏色塊,顯示如下

這裡寫圖片描述

Wall duration 是指此顏色塊代表的方法從開始到結束的耗時
CPU duration 是指 CPU的執行時間
可以發現CPU duration只有4ms,但Wall duration有18ms。
延遲這麼嚴重,我們來看看原因。
在選中的顏色塊上方,我們看到四個CPU都被顏色塊填充,表示此時4個CPU都有活幹,很忙。
我們選中一個CPU的顏色塊

這裡寫圖片描述

可以發現佔用CPU的應用是com.udinic.keepbusyapp
恩,對Systrace的介紹到此結束了,雖然還有些沒講,但Systrace的確只能看個概覽。
且慢,再加送一個tip, 點選右邊欄的Alerts, 你能看到所有的Alerts.

這裡寫圖片描述

通過這張圖我分析出
inefficient view alpha usage數量最多。Inflation during ListView recycling影響時間最長,耗時長達52ms。
inefficient view alpha usage是因為呼叫具體View的setAlpha方法,而View的setAlpha在Android中是很昂貴的操作。解決方法是用ARGB設定color代替直接呼叫setAlpha;如果是ImageView,呼叫ImageView#setImageAlpha;如果是自定義View,覆蓋hasOverlappingRendering()或者onSetAlpha()或者通過paint.setAlpha實現。詳細請參考文章1文章2, 文章3

Traceview

Traceview能夠在方法層面上分析APP的效能,非常強大。
可以通過,命令列或者GUI啟動,我用的是GUI啟動,點選AS的Android Device Monitor, 點選Devices欄目下面的 Start Method Profilling的圖示 , 在對話方塊中選擇,(我選的是trace Based Profilling,表示實時分析,會比較慢,但分析結果詳細),操作APP, 分析結束的時候,點選同一個圖示即可。更多操作請訪問官網
先看下介面

這裡寫圖片描述

列名 意義
Name 方法名,每個方法的顏色都不一樣。
Inclusive CPU Time 此方法佔用CPU的時間,Inclusive指包括呼叫的方法所
Exclusive CPU Time 此方法所佔用CPU的時間,Exclusive 指不包括呼叫的方法
Inclusive / Exclusive Real Time Real Time指方法從開始到結束消耗的時間,跟Systrace中的Wall duration一個意思。
Calls+Recursion 此方法被呼叫了多少次+多少次是遞迴呼叫
Calls / Total 子方法被此父方法呼叫的次數/子方法被呼叫的總次數

點選某條目下的parent 或者 child 方法時,會跳到該方法的條目。
想找出最影響效能的方法,可以點選Exclusive CPU time一欄,找出消耗時間最長的幾個方法。如果是應用的方法,直接看可不可優此方法。如果是系統方法,通過檢視其父方法,追溯至應用方法。
而檢視子方法,可以看出此方法到底做了什麼。
如果要找UI卡頓的原因,可以從 具體Adaper類#getView 具體View#ondraw, 具體View#onMeasure等方法入手。

方法執行時佔用了CPU,所以執行時間過長會造成UI渲染被延遲,從而應用不流暢。而GC同樣會佔用CPU,AS也同樣提供了檢視GC的工具:

Memory Profiling

點選AS中的Android Monitor, 選中Memory | CPU 一欄, 介面如下

這裡寫圖片描述

如圖所示,小幅的記憶體下降一般就是發生了GC。
點選左側的Heap dump,會生成記憶體中的所有物件的快照。

這裡寫圖片描述

列名 意義
Total Counts 記憶體中該類的物件個數
Heap Count 在該堆中該類的物件個數,左上角可以選擇App heap或Zygote heap
Sizeof 單個物件佔用的Shallow Size
Shallow Size 所有物件所佔用的Shallow Size
Retained Size 所有物件所佔用的Retained Size,即GC後會釋放的記憶體
instance 該類一個具體的物件
Reference Tree 引用這個物件的父物件,點選父物件,展開這些父物件的父物件

選中一行,點選右側的一個instance,可以在下方看到Refrence Tree介面

這裡寫圖片描述

在圖中可以看出MemoryActivity的一個instance在ListenerManager中被引用了。如果MemoryActivity已經不在Activity棧中了,這樣的引用就是記憶體洩漏。另外一個檢查記憶體洩漏的工具是leakcanary
通過檢視Retained Size和Reference Tree,我們可以知道哪些物件佔用了較多的記憶體,物件間的引用關係,進而分析是否可以優化資料結構,減少引用關係,以減少記憶體佔用和GC頻率。

Allocation Tracker

Memory | CPU一欄左側的另一個按鈕Allocation Tracker也是用於分析記憶體佔用。點選一次表示開始記錄,再次點選表示停止記錄。
在結果頁面的左上方點選餅狀圖。
可以選擇 group by Allocator,即按物件劃分。

這裡寫圖片描述

或者 group by method

這裡寫圖片描述

Allocator下面的餅狀圖最外圍的是具體的類,內部的是包名。圖中可以看出包或者物件佔用的記憶體大小或者個數,面積越大,佔用或者個數越多。選擇size可以檢視佔用記憶體最多的物件,選擇count可以檢視以及個數最多的物件。前者我們可以試著優化類,後者我們可以嘗試建立一個Object pool來複用物件。

從group by method可以看出,decode方法佔用的總記憶體達10.91M, 就有可能是方法內新建了太多物件,可以往這方面優化。

記憶體方面的tips:
1. Enums Enums比int佔的記憶體大得多,而且有替換方案@IntDef, 所以除了某些情況,比如你需要強制指定型別,不然的話int會更節省記憶體
2. 自動裝箱 自動裝箱指從基本型別自動轉換到物件形式的(比如int到Integer),鑑於基本型別使用的場景和次數都較多,所以需要儘量避免使用其自動裝箱的形式。
3. HashMap vs ArrayMap / Sparse*Array 如果我們需要使用int作為Map的value,可以使用SparseIntArray,比起使用HashMap對int自動裝箱,要省記憶體的多。如果要使用Object作為Map的key,除了HashMap,你也可以考慮使用ArrayMap,功能和HashMap一樣,但更省記憶體,點選瞭解原理。儘管時間效能上HashMap更勝一籌,但除非你要儲存1000個以上物件,否則他們使用起來幾乎一樣快。
4. 注意Context物件 因為Context在開發中的使用場景較多,所以最容易造成記憶體洩漏。Activity本身是一個heavy的物件,為了避免記憶體洩漏,可以穿ApplicationContext的話,就不要傳Activity了。
5. 避免非靜態內部類 非靜態內部類隱式持有外部類的引用,所以如果外部類不再被需要,但內部類仍在使用狀態,就造成了記憶體洩漏。特別是Activity類,在定義內部類的時候儘量定義成static的。

GPU Profiling

首先在手機的開發者選項頁面,點選GPU呈現模式分析(Profile GPU rendering),選中“In adb shell dumpsys gfxinfo” 然後在AS的Android Monitor 介面選中GPU一欄,確保左上方的暫停按鈕沒有選中,此時AS就開始按照選定的包名顯示GPU情況了。每一個條直線表示UI渲染中的一幀,不同的顏色表示不同的繪製階段。

這裡寫圖片描述

  • Draw(藍色) 執行的是View#onDraw()方法。這個階段的工作是建立DisplayList物件,這些物件稍後將被轉換成OpenGL命令,傳送到GPU。如果藍色較長,一般是因為較複雜的View, 或者短時間內invalidate了較多的View
  • Prepare (紫色) Lollipop才引入的階段,用於加快UI渲染,線上程RenderThread中執行。這個階段的任務是將第一步產生的display lists轉換成OpenGL命令,並傳送到GPU。此時UI thread將繼續處理下一幀。UI Thread給RenderThread傳遞所需的資源產生的耗時也記錄在此階段中。當有大量的資源要傳遞,比如很多/很heavy的display lists,這個階段耗費的時間會增多。
  • Process(紅色) 處理display lists,產生OpenGL命令,較多或者較複雜的display lists會使此階段耗時增加,因為很多View將被redraw。View被redraw的情況有invalidate或者之前覆蓋在上面的View現在被移走了。
  • Execute (黃色) 將OpenGL命令傳送給GPU, 這是個阻塞方法,因為CPU通過buffer將OpenGL命令傳送給GPU, 當處理完畢返回空的buffer。buffer的數量有限,所以當GPU很忙,buffer也用完了,CPU就需要等待GPU處理完返回一個空的buffer,才能繼續傳送OpenGL命令。因此如果這個階段耗時較多,一般是因為在繪製複雜的View。

在 Marshmallow 版本,增加了更多的顏色

這裡寫圖片描述
根據谷歌工程師John Reck提供的資訊,
圖中的Animation 是指所有通過Choreographer 註冊的CALLBACK_ANIMATION,包括
Choreographer#postFrameCallback View#postOnAnimation。這兩個函式在 view.animate(), ObjectAnimator, Transitions等場景中有用到。systrace中的Animation也是這個意思。
misc指的是接收到的vsync的時間戳和當前時間的延遲。
很多人都看到過Choreographer的log “Missed vsync by。。。ms skipping 。。。 frames”,這就是misc。換句話說,就是在記錄幀狀態時INTENDED_VSYNC和VSYNC的差別
要使用這個功能,你需要在手機的開發者選項中開啟GPU rendering
這裡寫圖片描述
此工具原理是ADB命令

adb shell dumpsys gfxinfo <PACKAGE_NAME>

如果你自己手動敲此命令,也會得到如下相關資訊
這裡寫圖片描述

如果我們的專案有自動化UI測試工具,就可以在構建伺服器上在一些UI互動後(列表滑動,複雜動畫)執行此命令,檢視“Janky Frames”等值是否有變化。這樣做能夠幫我們確定最近的幾次提交(commite)是否影響了效能,在產品釋出前發現和解決此問題。如果我們使用framestats作為關鍵字,還能獲得更多詳細的渲染資訊。
我們還能以其他方式展示此圖
在“Profile GPU Rendering”選項裡,有“On screen as bars”這個選項,選中它,在手機螢幕上就會出現三個影象,分別代表StatusBar,NavBar和當前程式的Activity的GPU Rending資訊,以綠線指示16ms的渲染閾值。
這裡寫圖片描述

在右側這張圖,我們看到有些幀超過了綠線,即說明渲染時間超過了16ms。這些“越界”的幀大部分是藍色,我們大概可以認為是因為繪製了太多或者太複雜的View。我滑動了一下此介面的資訊流,的確有多種型別的View.有些會被重繪,有些比較複雜。所以那些“越界”的幀可能是因為正在繪製複雜的View.

Hierarchy Viewer

我非常喜歡這個工具,很遺憾好多人都沒有使用過。
Hierarchy Viewer能顯示效能情況,螢幕上完整的View結構,以及View的屬性。如果你單獨執行Hierarchy Viewer,而不是從Android Monitor啟動,你也能獲取主題資訊,所有的style屬性。當我設計以及優化佈局時就會這麼做。
這裡寫圖片描述
在上圖中間,我們能看到樹形的View結構。View結構可以很寬,但是如果View層數太深(比如10層左右),就會增加昂貴的layout和measurement操作。測量View時呼叫View.onMeasure, 佈局子View時呼叫View.onLayout。這兩個命令會向子View傳遞。有些佈局會呼叫兩次這兩個命令,比如RelativeLayout和某些LinearLayout,如果View是巢狀的,命令傳遞的次數就會以指數增長。
在右下角,是佈局實際的效果,標註了每個View放置的位置,我們可以在此圖或者樹形圖中選中一個View, 然後在左邊檢視所有的屬性。當設計佈局時,我有時候不能確定為什麼某個View會被佈局在那裡。有了這個工具,我就能在樹形圖上找到它,選中,然後就能在預覽圖中看到它的位置。在設計有趣的動畫時,我會檢視螢幕中View的最終測量資料,據此精確的移動View。我也能發現被無意蓋掉而看不見的View。
這裡寫圖片描述
對於每一個View,我們都可以獲知它本身以及子View的measure/layout/draw時間。顏色指示它與其他View比較時,效能情況如何,有助於找出View繪製過程中最慢的一環。而且我們能看到View的預覽,能在樹形圖中看到此View建立的步驟,找出和刪除冗餘的步驟。影響效能的一個重要原因是Overdraw。
Overdraw

正如GPU Profiling所呈現的,如果GPU需要在螢幕上進行大量的繪製,Execute階段(GPU中的黃色部分)會花費更多的時間,繪製每一幀所需的時間也就拉長了。在已繪製的螢幕上再度繪製,就叫做過度繪製,比如在紅色的背景上繪製一個黃色的按鈕。GPU需要先繪製紅色的背景,然後繪製黃色的按鈕,就造成了過度繪製。如果是很多層的過度繪製,GPU的工作負荷就很重,就會影響到16ms的效能指標。
這裡寫圖片描述
啟用開發者選項中的“除錯GPU過度繪製”,所有的過度繪製就會按照嚴重程度用不同的顏色展示。1~2層的過度繪製算合理,有小範圍的淺紅也還能接受,但是如果紅色的區域太多,就需要注意了。舉幾個例子:
這裡寫圖片描述
左圖中,有一個綠色的列表,通常意味著還行,但頂部的覆蓋區域顯示為紅色,就需要解決了。右圖中,整個列表都是淺紅。兩個圖的列表都不透明,都有2~3層的過度繪製。一個可能的原因是持有Activity/Fragment的視窗(window),ListView,以及每個ListView的item都有各自的背景。解決的辦法是隻設定一個背景。

注意:預設的主題設定了視窗的背景色,如果你的Activity包含的不透明的佈局能覆蓋全螢幕,你就可以通過移除視窗的背景色來減少過度繪製。可以在主題中設定,或者在onCreate方法中呼叫 getWindow().setBackgroundDrawable(null)

利用 Hierarchy Viewer, 你能匯出所有層級到 PSD 檔案。用Photoshop開啟此檔案,檢視不同的層級,你能發現佈局中所有的過度繪製. 請利用這些資訊去掉冗餘的過度繪製,不要除錯GPU過度繪製時顯示綠色就覺得可以了,爭取藍色。

Alpha

使用透明屬性可能會影響效能。為了理解這句話,我們來看看當給View設定alpha屬性時,會發生些什麼?初始佈局如下圖所示
這裡寫圖片描述
此佈局包含三個互相有重疊的ImageView。如果對佈局設定了Alpha屬性,也就是呼叫 setAlpha(),“直接/簡單粗暴的方案”是對各個子View(在此例中是三個ImageView)設定Alpha。這樣三個ImageView會依據設定的Alpha重新繪製並進入幀緩衝。結果如下:
這裡寫圖片描述
這不是我們想要的。
因為每個Image都設定了alpha,重疊的ImageView就混合在一起了。幸運的是,安卓系統找到了解決方案。佈局將會被複制到離屏緩衝,並對緩衝區整體設定Alpha,處理結果將會複製回幀緩衝。結果如下:
這裡寫圖片描述
但是。。這樣做是有代價的。
先在離屏快取區繪製View,然後再繪製幀快取,實際上是增加了一層未被GPU Profiling檢測到的過度繪製。安卓系統不知道何時使用此方案,何時使用之前提到的方案,所以只能預設使用此方案。但我們仍有辦法設定Alpha並且避免離屏快取造成的複雜性。
  • TextViews - 呼叫 setTextColor() 而不是 setAlpha(). 使用alpha 通道設定字型顏色, 繪製文字時會直接使用此alpha。
  • ImageView - 使用 setImageAlpha() 而不是 setAlpha() 。理由同上。
  • 自定義 View - 如果你的自定義View不支援重疊子View, 就無須理會此合成操作, 因為子View不會像上面的例子混合在一起. 覆寫hasOverlappingRendering() 方法,並返回 false,我們就在告訴安卓系統:使用“直接/簡單粗暴的方案”。如果要自己處理alpha屬性,就覆寫onSetAlpha()方法,並返回true。詳細請參考文章1文章2, 文章3
    Hardware Acceleration

    安卓蜂巢版引進了硬體加速功能,我們由此有了新的繪製模型來渲染APP。硬體加速引入了DisplayList結構,通過記錄 View 的繪製命令來加快渲染。但是有一個很重要的功能是開發者往往遺漏或者沒有正確的使用的—View layers。
    使用View layer,我們能在離屏快取中渲染View(就像上面應用Alpha通道的例子),並隨意操作View。這個功能在動畫的時候很有用,能讓複雜的View在動畫時更順暢。 不使用layer的話, 對View執行動畫時會先改變其屬性(比如 x 座標,伸縮比,透明度等)然後invalidate. 對於複雜的View, invalidate 操作會傳遞到各個子View, 各個子View都會重繪,是個昂貴的操作。而Hardware提供的View layer 則是在GPU中為View生成一個紋理(Texture)。利用texture,某些操作(改變x/y軸座標,旋轉,alpha等)就不需要invalidate了。這一切意味著,我們能在一個複雜的View上執行動畫,而不需要invalidate! 這會讓動畫順暢很多。下面的例子將告訴你如何做:
// Using the Object animator
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f);
objectAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
objectAnimator.start();

// Using the Property animator
view.animate().translationX(20f).withLayer().start();

很簡單,是不是?
不過使用hardware layer時需要注意以下幾點:

  • Hardware layer 佔用了GPU有限的記憶體,所以請只在動畫等需要的場合使用Hardware layer,使用完後及時清理。上例中,使用ObjectAnimator時,我設定了一個監聽器,在動畫結束時移除layer。使用Property animator時, 我使用了withLayers()方法, 此方法會在動畫開始時自動建立layer並在動畫結束時移除。

  • 如果你在應用了hardware layer以後改變了View的屬性, 就會invalidate hardware layer 並在離屏快取重新渲染一遍View。這種情況在執行Hardware Layer未優化的操作時會發生 ( 到目前為止, 被優化的操作包括: 旋轉, 伸縮, 座標設定, 座標偏移, pivot(樞軸) 和 透明度)。比如 , 你對使用了hardware layer的View執行動畫 ,一邊位移一邊更新 View 的背景色,就會導致hardware layer不停的更新. 在這種情況下,更新hardware layer的開銷會抵消掉使用它帶來的好處。

在第二種情況下,我們可以檢視hardware layer更新的情況。即在開發者選項中啟用 “顯示硬體層更新”
這裡寫圖片描述
啟用後,View在更新其hardware layer時會以綠光閃爍。不久前我的一個ViewPager滑動起來不流暢時我就啟動了此選項. 下圖是我當時所看到的:
這裡寫圖片描述
在整個的滑動過程中,兩個Page都顯示綠色!

翻譯了大半,待續