1. 程式人生 > >Android效能分析工具(二)

Android效能分析工具(二)

這裡寫圖片描述

DDMS

DDMS 的全稱是Dalvik Debug Monitor Service,是Android 開發環境中的Dalvik 虛擬機器除錯監控服務

這裡寫圖片描述

這裡寫圖片描述

HierarchyViewer

UI效能分析工具,分析佈局檔案的效能,層級巢狀

UI佈局複雜程度及冗餘分析,View巢狀的冗餘層級

View的效能指標:測量、佈局、繪製的渲染時間

1、invalidate Layout按鈕

invalidate(),強制重新整理

2、requestLayout按鈕

requestLayout(),重新測量,佈局

使用GPU過度繪製分析UI效能

開發者選項中的GPU過度繪製工具(Show GPU Overdraw)

這裡寫圖片描述

使用GPU呈現模式圖及FPS考核UI效能

開發者選項中的GPU呈現模式分析,Profile GPU Rendering

這裡寫圖片描述

Android Monitor

這裡寫圖片描述

這裡寫圖片描述

TraceView

TraceView 簡介

Traceview 是Android 平臺特有的資料採集和分析工具,它主要用於分析Android 中應用程式的hotspot(瓶頸)。Traceview 本身只是一個數據分析工具,而資料的採集則需要使用Android SDK 中的Debug 類或者利用DDMS 工具。二者的用法如下:

開發者在一些關鍵程式碼段開始前呼叫Android SDK 中Debug 類的startMethodTracing 函式,並在關鍵程式碼段結束前呼叫stopMethodTracing 函式。這兩個函式執行過程中將採集執行時間內該應用所有執行緒(注意,只能是

Java執行緒)的函式執行情況,並將採集資料儲存到/mnt/sdcard/下的一個檔案中。開發者然後需要利用SDK 中的Traceview工具來分析這些資料。

藉助Android SDK 中的DDMS 工具。DDMS 可採集系統中某個正在執行的程序的函式呼叫資訊。對開發者而言,此方法適用於沒有目標應用原始碼的情況。DDMS 工具中Traceview 的使用如下圖所示。

觀察CPU的執行情況,測試的程序中每個執行緒執行的時間線,執行緒中各個方法的呼叫資訊(CPU使用時間、呼叫次數等)

可以方便的檢視執行緒的執行情況,某個方法執行時間、呼叫次數、在總體中的佔比等,從而定位效能點

一般Traceview可以定位兩類效能問題

  • 方法調運一次需要耗費很長時間導致卡頓
  • 方法調運一次耗時不長,但被頻繁調運導致累計時長卡頓

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

點選上圖中所示按鈕即可以採集目標程序的資料。當停止採集時,DDMS 會自動觸發Traceview 工具來瀏覽採集資料

下面,我們通過一個示例程式介紹Traceview 的使用。

例項程式如下圖所示:介面有4 個按鈕,對應四個方法。

這裡寫圖片描述

點選不同的方法會進行不同的耗時操作。

public class MainActivity extends ActionBarActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }

        public void method1(View view) {
            int result = jisuan();
            System.out.println(result);
        }

        private int jisuan() {
            for (int i = 0; i < 10000; i++) {
                System.out.println(i);
            }
            return 1;
        }

        public void method2(View view) {
            SystemClock.sleep(2000);
        }

        public void method3(View view) {
            int sum = 0;
            for (int i = 0; i < 1000; i++) {
                sum += i;
            }
            System.out.println("sum=" + sum);
        }

        public void method4(View view) {
            Toast.makeText(this, "" + new Date(), 0).show();
        }
    }
  • 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
  • 34
  • 35
save_snippets.png
  • 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
  • 34
  • 35

我們分別點選按鈕一次,要求找出最耗時的方法。點選前通過DDMS 啟動Start Method Profiling 按鈕。
這裡寫圖片描述

然後依次點選4 個按鈕,都執行後再次點選上圖中紅框中按鈕,停止收集資料。

接下來我們開始對資料進行分析。

當我們停止收集資料的時候會出現如下分析圖表。該圖表分為2 大部分,上面分不同的行,每一行代表一個執行緒的執行耗時情況。main 執行緒對應行的的內容非常豐富,而其他執行緒在這段時間內幹得工作則要少得多。圖表的下半部分是具體的每個方法執行的時間情況。顯示方法執行情況的前提是先選中某個執行緒。

這裡寫圖片描述

我們主要是分析main 執行緒。

上面方法指標引數所代表的意思如下:

列名 描述
Name 該執行緒執行過程中所呼叫的函式名
Incl Cpu Time 某函式佔用的CPU 時間,包含內部呼叫其它函式的CPU 時間
Excl Cpu Time 某函式佔用的CPU 時間,但不含內部呼叫其它函式所佔用的CPU 時間
Incl Real Time 某函式執行的真實時間(以毫秒為單位),包含呼叫其它函式所佔用的真實時間
Excl Real Time 某函式執行的真實時間(以毫秒為單位),不含呼叫其它函式所佔用的真實時間
Call+Recur Calls/Total 某函式被呼叫次數以及遞迴呼叫佔總呼叫次數的百分比
Cpu Time/Call 某函式呼叫CPU 時間與呼叫次數的比。相當於該函式平均執行時間
Real Time/Call 同CPU Time/Call 類似,只不過統計單位換成了真實時間

 
我們為了找到最耗時的操作,那麼可以通過點選Incl Cpu Time,讓其按照時間的倒序排列。我點選後效果如下圖:

這裡寫圖片描述

通過分析發現:method1 最耗時,耗時2338 毫秒。

這裡寫圖片描述

那麼有了上面的資訊我們可以進入我們的method1 方法檢視分析我們的程式碼了

生成.trace檔案

android.os.Debug類,其中重要的兩個方法Debug.startMethodTracing()和Debug.stopMethodTracing()。這兩個方法用來建立.trace檔案,將從Debug.startMethodTracing()開始,到Debug.stopMethodTracing()結束,期間所有的呼叫過程儲存在.trace檔案中,包括呼叫的函式名稱和執行的時間等資訊。

這裡寫圖片描述

這裡寫圖片描述

dmtracedump

dmtracedump -g result.png target.trace  //結果png檔案  目標trace檔案
  • 1
save_snippets.png
  • 1

Allocation Tracker

追蹤記憶體的分配,追蹤記憶體物件的來源,通過這個工具我們可以很方便的知道程式碼分配了哪類物件、在哪個執行緒、哪個類、哪個檔案的哪一行

執行DDMS,只需簡單的選擇應用程序並單擊Allocation tracker 標籤,就會開啟一個新的視窗,單擊“Start Tracing”按鈕;

然後,讓應用執行你想分析的程式碼。執行完畢後,單擊“Get Allocations”按鈕,一個已分配物件的列表就會出現第一個表格中。

單擊第一個表格中的任何一項,在表格二中就會出現導致該記憶體分配的棧跟蹤資訊。通過allocation tracker,不僅知道分配了哪類物件,還可以知道在哪個執行緒、哪個類、哪個檔案的哪一行。

這裡寫圖片描述

Systrace

這裡寫圖片描述
Systrace其實有些類似Traceview,它是對整個系統進行分析

DDMS->Capture system wide trace using Android systrace

Heap

這裡寫圖片描述

記憶體監測工具,分析記憶體使用情況,檢視當前記憶體快照,便於對比分析哪些物件有可能是洩漏了的

heap 工具可以幫助我們檢查程式碼中是否存在會造成記憶體洩漏的地方。用heap 監測應用程序使用記憶體情況的步驟如下:

  • 啟動eclipse 後,切換到DDMS 透檢視,並確認Devices 檢視、Heap 檢視都是開啟的

  • 點選選中想要監測的程序,比如system_process 程序;

  • 點選選中Devices 檢視介面中最上方一排圖示中的“Update Heap”圖示;

  • 點選Heap 檢視中的“Cause GC”按鈕;

  • 此時在Heap 檢視中就會看到當前選中的程序的記憶體使用量的詳細情況。

說明:

  • 點選“Cause GC”按鈕相當於向虛擬機器請求了一次gc 操作;

  • 當記憶體使用資訊第一次顯示以後,無須再不斷的點選“Cause GC”,Heap 檢視介面會定時重新整理,在對應用的不斷的操作過程中就可以看到記憶體使用的變化;

  • 記憶體使用資訊的各項引數根據名稱即可知道其意思,在此不再贅述。

如何才能知道我們的程式是否有記憶體洩漏的可能性呢。這裡需要注意一個值:Heap 檢視中部有一個Type叫做data object,即資料物件,也就是我們的程式中大量存在的類型別的物件。在data object 一行中有一列是“Total Size”,其值就是當前程序中所有Java 資料物件的記憶體總量,一般情況下,這個值的大小決定了是否會有記憶體洩漏。可以這樣判斷:

  • 不斷的操作當前應用,同時注意觀察data object 的Total Size 值

  • 正常情況下Total Size 值都會穩定在一個有限的範圍內,也就是說由於程式中的的程式碼良好,沒有造成物件不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多物件,而在虛擬機器不斷的進行GC 的過程中,這些物件都被回收了,記憶體佔用量會會落到一個穩定的水平;

  • 反之如果程式碼中存在沒有釋放物件引用的情況,則data object 的Total Size 值在每次GC 後不會有明顯的回落,隨著操作次數的增多Total Size 的值會越來越大,直到到達一個上限後導致程序被kill 掉

  • 此處以system_process 程序為例,在我的測試環境中system_process 程序所佔用的記憶體的data object
    的Total Size 正常情況下會穩定在2.2~2.8 之間,而當其值超過3.55 後進程就會被kill

  • 總之,使用DDMS 的Heap 檢視工具可以很方便的確認我們的程式是否存在記憶體洩漏的可能性

Leakcanary

Square出品,記憶體洩露監測神器,GitHub地址

Eclipse Memory Analyzer(MAT)

這裡寫圖片描述

記憶體分析工具,這個工具分為Eclipse外掛版和獨立版兩種,如果你是使用Eclipse開發的,那麼可以使用外掛版MAT,非常方便。如果你是使用Android Studio開發的,那麼就只能使用獨立版的MAT了

HPROF檔案

這裡寫圖片描述

HPROF檔案是MAT能識別的檔案,HPROF檔案儲存的是特定時間點,java程序的記憶體快照

點選Dump HPROF file按鈕,生成HPROF檔案,這個檔案記錄著我們應用程式內部的所有資料。但是目前MAT還是無法開啟這個檔案的,我們還需要將這個HPROF檔案從Dalvik格式轉換成J2SE格式,使用hprof-conv命令就可以完成轉換工作

hprof-conv dump.hprof converted-dump.hprof 
  • 1
save_snippets.png
  • 1

Histogram

Histogram:列出記憶體中每個物件的名字、數量以及大小
Shallow Heap:當前物件自己所佔記憶體的大小,不包含引用關係的

析大記憶體的物件,分析物件的數量

這裡寫圖片描述

Dominator Tree

Dominator Tree:列出最大的物件以及其依賴存活的Object,並且我們可以分析物件之間的引用結構

Retained Heap

表示這個物件以及它所持有的其它引用(包括直接和間接)所佔的總記憶體

在每一行的最左邊都有一個檔案型的圖示,這些圖示有的左下角帶有一個紅色的點,有的則沒有。帶有紅點的物件就表示是可以被GC Roots訪問到的,可以被GC Root訪問到的物件都是無法被回收的。帶紅點的物件最右邊都有寫一個System Class,說明這是一個由系統管理的物件,並不是由我們自己建立並導致記憶體洩漏的物件

搜尋大記憶體物件通向GC Roots的路徑,因為記憶體佔用越高的物件越值得懷疑

GC Roots reference chain(引用鏈)的起點,是一個在current thread(當前執行緒)的call stack(呼叫棧)上的物件(例如方法引數和區域性變數),或者是執行緒自身或者是system class loader(系統類載入器)載入的類以及native code(原生代碼)保留的活動物件。所以GC Roots是分析物件為何還存活於記憶體中的利器。

這裡寫圖片描述

這裡寫圖片描述

dumpsys meminfo命令

adb shell dumpsys meminfo <package_name|pid> [-d]
  • 1
save_snippets.png
  • 1

命令後面帶-d標誌會打印出更多關於記憶體使用的資訊

adb shell dumpsys meminfo com.google.android.apps.maps -d
  • 1
save_snippets.png
  • 1
C:\Users\AllenIverson>adb shell dumpsys meminfo com.qq.googleplay -d
Applications Memory Usage (kB):
Uptime: 588545781 Realtime: 1460567078

** MEMINFO in pid 19204 [com.qq.googleplay] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap        0        0        0        0    20480     9524    10955
  Dalvik Heap    23402    22936        0        0    40714    38963     1751
 Dalvik Other      729      728        0        0
        Stack      392      392        0        0
    Other dev        5        0        4        0
     .so mmap     1586      232      468        0
    .apk mmap      281        0       52        0
    .ttf mmap      262        0      248        0
    .dex mmap     5500        0     5356        0
    .oat mmap     2606        0      916        0
    .art mmap     2350      924      800        0
   Other mmap     1342        4      884        0
      Unknown     7798     7748        0        0
        TOTAL    46253    32964     8728        0    61194    48487    12706

 Objects
               Views:      261         ViewRootImpl:        1
         AppContexts:        3           Activities:        1
              Assets:        4        AssetManagers:        4
       Local Binders:        7        Proxy Binders:       16
       Parcel memory:        5         Parcel count:       23
    Death Recipients:        0      OpenSSL Sockets:        0

 Dalvik
         isLargeHeap:    false

 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0
  • 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
  • 34
  • 35
  • 36
  • 37
save_snippets.png
  • 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
  • 34
  • 35
  • 36
  • 37

一般我們只需要關心Pss Total和Private Dirty兩列資料,在某些情況下,Private Clean和Heap Alloc兩列資料可能會提供你感興趣的資料

adb shell dumpsys batterystats 電量狀態
  • 1
save_snippets.png
  • 1

Lint工具

這裡寫圖片描述

使用Lint進行資源及冗餘UI佈局等優化,Lint 有自動修復、提示建議和直接跳轉到問題處的功能

整合到androidstudio,點選工具欄的Analysis -> Inspect Code
記憶體抖動:短時間內有大量頻繁的物件建立與釋放操作

Lint是Android提供的一個靜態掃描應用原始碼並找出其中的潛在問題的一個強大的工具

執行Lint:點選工具欄的Analysis -> Inspect Code

ProGuard

混淆程式碼,壓縮和優化程式碼,apk瘦身

GC列印

當發生GC垃圾回收的時候,會在logcat列印日誌

I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms
  • 1
save_snippets.png
  • 1

Investigating Your RAM Usage