前言
Android Profiler能夠提供關于應用 CPU、內存和網絡的實時數據。
要打開 Android Profiler 窗口,請按以下步驟操作:
1. 點擊工具欄中的 Android Profiler(也可以點擊 View > Tool Windows > Android Profiler )。
2. 在 Android Profiler 窗口頂部選擇想要分析的設備和應用進程,如下圖所示。
上圖中各個數字對應的含義:
① 要分析的設備。
② 要分析的應用進程。
③ 時間線縮放控件。
④ 實時更新跳轉按鈕。
⑤ Event時間線,包括Activity狀態、用戶輸入Event和屏幕旋轉Event。
如果顯示“Advanced profiling is unavailable for the selected process”,可以在頂層工具欄中點擊 Profile 'app' 來運行,或者在運行配置中啟用高級分析,按以下步驟操作:
1. 選擇 Run > Edit Configurations。
2. 在左側窗格中選擇您的應用模塊。
3. 點擊 Profiling 標簽,然后勾選 Enable advanced profiling。
重新構建并運行應用即可。
二. CPU Profiler
CPU Profiler 可幫助實時檢查應用的 CPU 使用率和線程 Activity,并記錄函數跟蹤,以便優化和調試應用代碼。
如上圖所示,CPU Profiler 的默認視圖包括以下內容:
① Event 時間線: 顯示應用中在其生命周期轉換的 Activity,并顯示用戶與設備的交互,包括屏幕旋轉 Event。
② CPU 時間線: 顯示應用的實時 CPU 使用率(占總可用 CPU 時間的百分比)以及應用使用的總線程數。 此時間線還顯示其他進程的 CPU 使用率(如系統進程或其他應用),以便可以將其與自己的應用使用率進行對比。 通過沿時間線的水平軸移動鼠標,還可以檢查歷史 CPU 使用率數據。
③ 線程 Activity 時間線: 列出屬于應用進程的每個線程。下面說明不同的顏色對應的含義:
綠色: 表示線程處于活動狀態或準備使用 CPU。 即,它正在“運行中”或處于“可運行”狀態。
黃色: 表示線程處于活動狀態,但它正在等待一個 I/O 操作(如磁盤或網絡 I/O),然后才能完成它的工作。
灰色: 表示線程正在休眠且沒有消耗任何 CPU 時間。 當線程需要訪問尚不可用的資源時偶爾會發生這種情況。 線程進入自主休眠或內核將此線程置于休眠狀態,直到所需的資源可用。
④ 記錄配置: 選擇分析器記錄函數跟蹤的方式,如下:
Sampled: 以固定周期記錄。在應用執行期間頻繁捕獲應用的調用堆棧。 分析器比較捕獲的數據集以推導與應用代碼執行有關的時間和資源使用信息。 基于“Sampled”的跟蹤的問題是,如果應用在捕獲調用堆棧后進入一個函數并在下一次捕獲前退出該函數,則分析器不會記錄該函數調用。 如果對此類生命周期很短的跟蹤函數感興趣,應使用“Instrumented”跟蹤。
Instrumented: 以函數調用時間為周期記錄。在運行時設置應用以在每個函數調用的開始和結束時記錄時間戳。 它收集時間戳并進行比較,以生成函數跟蹤數據,包括時間信息和 CPU 使用率。 注意,與設置每個函數關聯的開銷會影響運行時性能,并可能會影響分析數據,對于生命周期相對較短的函數,這一點更為明顯。 此外,如果應用短時間內執行大量函數,則分析器可能會迅速超出它的文件大小限制,且不能再記錄更多的跟蹤數據。
Edit configurations: 允許更改上述“Sampled”和“Instrumented”記錄配置的某些默認值,并將它們另存為自定義配置。
⑤ 記錄按鈕: 用于開始和停止記錄函數跟蹤。
注:分析器還會報告 Android Studio 和 Android 平臺添加到您的應用進程(如 JDWP、Profile Saver、Studio:VMStats、Studio:Perfa 以及 Studio:Heartbeat)的線程 CPU 使用率。
選擇 Sampled 或 Instrumented ,然后點擊Record開始記錄函數跟蹤,點擊Stop recording結束,如下圖所示。
① 選擇時間范圍: 確定要在跟蹤窗格中檢查所記錄時間范圍的哪一部分。 當首次記錄函數跟蹤時,CPU Profiler 將在 CPU 時間線中自動選擇完整長度。 如果想僅檢查所記錄時間范圍一小部分的函數跟蹤數據,可以點擊并拖動突出顯示的區域邊緣以修改其長度。
② 時間戳: 用于表示所記錄函數跟蹤的開始和結束時間(相對于分析器從設備開始收集 CPU 使用率信息的時間)。 可以點擊時間戳以自動選擇完整記錄。
③ 跟蹤窗格: 用于顯示所選的時間范圍和線程的函數跟蹤數據。
④ 通過調用圖表、火焰圖、 Top Down 樹或Bottom Up 樹的形式顯示函數跟蹤。
⑤ 確定如何測量每個函數調用的時間信息:
Wall clock time:實際經過的時間。
Thread time:實際經過的時間減去線程沒有消耗 CPU 資源的時間。
Call Chart 標簽提供函數跟蹤的圖形表示形式,其中,水平軸表示函數調用(或調用方)的時間,并沿垂直軸顯示其被調用者。 對系統 API 的函數調用顯示為橙色,對應用自有函數的調用顯示為綠色,對第三方 API(包括 Java 語言 API)的函數調用顯示為藍色。 下圖展示了一個調用圖表示例,并描繪了給定函數的 Self time、Children time 以及總時間的概念。
提示:若要跳轉到某個函數的源代碼,請右鍵點擊該函數并選擇 Jump to Source。
Flame Chart 標簽提供一個倒置的調用圖表,其中水平軸不再代表時間線,它表示每個函數相對的執行時間。
下面說明此概念,考慮下圖中的調用圖表。注意函數 D 多次調用 B(B1、B2 和 B3),其中一些對 B 的調用也調用了 C(C1 和 C3)。
由于 B1、B2 和 B3 共享相同的調用方順序 (A → D → B),因此可將它們匯總在一起,如下所示。 同樣,將 C1 和 C3 匯總在一起,因為它們也共享相同的調用方順序 (A → D → B → C)。注意未包含 C2,因為它具有不同的調用方順序 (A → D → C)。
匯總的函數調用用于創建火焰圖,如下圖所示。注意,對于火焰圖中任何給定的函數調用,首先顯示消耗最多 CPU 時間的被調用方。
2.2.3 使用 Top Down 和 Bottom Up 檢查跟蹤
Top Down 標簽顯示一個函數調用列表,在該列表中展開函數節點會顯示函數的被調用方。 下圖顯示上面調用圖表對應的“Top Down”圖表。圖表中的每個箭頭都從調用方指向被調用方。
如上圖所示,在“Top Down”標簽中展開函數 A 的節點可顯示它的被調用方,即函數 B 和 D。 然后,展開函數 D 的節點可顯示它的被調用方,即函數 B 和 C 等等。
Top Down 標簽提供以下信息以說明在每個函數調用上所花費的 CPU 時間:
Self: 表示函數調用在執行自己的代碼(而非被調用方的代碼)上所花的時間。
Children: 表示函數調用在執行自己的被調用方(而非自己的代碼)上所花的時間。
總和: 函數的 Self 和 Children 時間的總和。 表示應用在執行函數調用上所花的總時間。
Bottom Up 標簽顯示一個函數調用列表,在該列表中展開函數節點將顯示函數的調用方,如下圖所示。
如上圖所示,在“Bottom Up”樹中打開函數 C 的節點可顯示它的調用方 B 和 D。 注意,盡管 B 調用 C 兩次,但在“Bottom Up”樹中展開函數 C 的節點時,B 僅顯示一次。 然后,展開 B 的節點顯示其調用方,即函數 A 和 D。
注:當分析器到達文件大小限制時,Android Studio 將停止收集新數據(但不會停止記錄)。
要創建或編輯自定義配置,或檢查現有的默認配置,可通過記錄配置下拉菜單中選擇 Edit configurations 來打開 CPU Recording Configurations 對話框,如下圖所示。
可以在左側窗格中選擇現有配置來檢查其設置,也可按如下方式創建一個新的記錄配置:
1. 點擊對話框左上角的 ? 。
2. 為配置命名。
3. 在 Trace Technology 部分選擇 Sampled 或 Instrumented。
4. 對于“Sampled”記錄配置,以微秒 (μs) 為單位指定 Sampling interval。 此值表示應用調用堆棧的每個抽樣之間的持續時間。
5. 對于寫入連接設備的記錄數據,以兆字節 (MB) 為單位指定 File size limit。 當您停止記錄時,Android Studio 將解析此數據并將其顯示在分析器窗口中。
注: 如果使用運行 API 級別 26 或更高版本的連接設備,則對于跟蹤數據的文件大小沒有限制,此值可忽略。
6. 點擊 Apply 或 OK。 如果更改了其他記錄配置,則也將保存這些更改。
三. Memory Profiler
Memory Profiler 是可幫助識別導致應用卡頓、凍結甚至崩潰的內存泄漏和流失。 它顯示一個應用內存使用量的實時圖表,可以捕獲堆轉儲、強制執行垃圾回收以及跟蹤內存分配。
如上圖所示,Memory Profiler 的默認視圖包括以下各項:
① 強制執行垃圾回收 Event 。
② 捕獲堆轉儲。
③ 記錄內存分配情況。 此按鈕僅在運行 Android 7.1 或更低版本的設備時才會顯示。
④ 放大/縮小時間線。
⑤ 跳轉至實時內存數據。
⑥ Event 時間線,其顯示 Activity 狀態、用戶輸入 Event 和屏幕旋轉 Event。
⑦ 內存使用量時間線,包含以下內容:
顯示每個內存類別使用多少內存的堆疊圖表,如左側的 y 軸以及頂部的彩色鍵所示。
虛線表示分配的對象數,如右側的 y 軸所示。
用于表示每個垃圾回收 Event 的圖標。
如上圖,內存計數中的類別如下所示:
Java:從 Java 或 Kotlin 代碼分配的對象內存。
Native:從 C 或 C++ 代碼分配的對象內存。
Graphics:圖形緩沖區隊列向屏幕顯示像素(包括 GL 表面、GL 紋理等等)所使用的內存。 (注意,這是與 CPU 共享的內存,不是 GPU 專用內存。)
Stack: 應用中的原生堆棧和 Java 堆棧使用的內存,通常與應用運行多少線程有關。
Code:應用用于處理代碼和資源(如 dex 字節碼、已優化或已編譯的 dex 碼、.so 庫和字體)的內存。
Other:應用使用的系統不確定如何分類的內存。
Allocated:應用分配的 Java/Kotlin 對象數。 沒有計入 C 或 C++ 中分配的對象。
注:目前,Memory Profiler 還會顯示應用中的一些誤報的原生內存使用量,而這些內存實際上是分析工具使用的。 對于大約 100000 個對象,最多會使報告的內存使用量增加 10MB。
Memory Profiler 可顯示有關對象分配的以下信息:
分配哪些類型的對象以及它們使用多少空間。
每個分配的堆疊追蹤,包括在哪個線程中。
對象在何時被取消分配(Android 8.0+)。
如果設備運行 Android 8.0 或更高版本,可以按照下述方法查看對象分配: 只需點擊并按住時間線,并拖動選擇想要查看分配的區域,如下圖所示:
如果設備運行 Android 7.1 或更低版本,則在 Memory Profiler 工具欄中點擊 Record memory allocations 。 操作完成后,點擊 Stop recording 以查看分配。如下圖所示:
在選擇一個時間線區域后,已分配對象的列表將顯示在時間線下方,按類名稱進行分組,并按其堆計數排序。
注:在 Android 7.1 及更低版本上,最多可以記錄 65535 個分配。 如果記錄超出此限值,則記錄中僅保存最新的 65535 個分配。 在 Android 8.0 及更高版本中,沒有此限制。
要檢查分配記錄,請按以下步驟操作:
1. 瀏覽列表以查找堆計數異常大且可能存在泄漏的對象。 點擊 Class Name 列標題可以按字母順序排序。 然后點擊一個類名稱。 此時在右側將出現 Instance View 窗格,顯示該類的每個實例。
2. 在 Instance View 窗格中,點擊一個實例。 此時下方將出現 Call Stack 標簽,顯示該實例被分配到何處以及在哪個線程中,如下圖所示。
3. 在 Call Stack 標簽中,雙擊任意行以在編輯器中跳轉到該代碼。
默認情況下,左側的分配列表按類名稱排列。 在列表頂部,可以使用右側的下拉列表在以下排列方式之間進行切換:
Arrange by class:基于類名稱對所有分配進行分組。
Arrange by package:基于軟件包名稱對所有分配進行分組。
Arrange by callstack:將所有分配分組到其對應的調用堆棧。
堆轉儲顯示在捕獲堆轉儲時應用中哪些對象正在使用內存。 特別是在長時間的用戶會話后,堆轉儲會顯示您認為不應再位于內存中卻仍在內存中的對象,從而幫助識別內存泄漏。 在捕獲堆轉儲后,可以查看以下信息:
應用已分配哪些類型的對象,以及每個類型分配多少。
每個對象正在使用多少內存。
在代碼中的何處仍在引用每個對象。
對象所分配到的調用堆棧。 (目前,如果在記錄分配時捕獲堆轉儲,則只有在 Android 7.1 及更低版本中,堆轉儲才能使用調用堆棧。)
要捕獲堆轉儲,在 Memory Profiler 工具欄中點擊 Dump Java heap 。 在轉儲堆期間,Java 內存量可能會暫時增加, 這很正常,因為堆轉儲與您的應用發生在同一進程中,并需要一些內存來收集數據。
堆轉儲顯示在內存時間線下,顯示堆中的所有類類型,如下圖所示。
要檢查堆,請按以下步驟操作:
1. 瀏覽列表以查找堆計數異常大且可能存在泄漏的對象。 點擊 Class Name 列標題可以按字母順序排序。 然后點擊一個類名稱。 此時在右側將出現 Instance View 窗格,顯示該類的每個實例。
2. 在 Instance View 窗格中,點擊一個實例,此時下方將出現 References,顯示該對象的每個引用。點擊實例名稱旁的箭頭可以查看其所有字段,然后點擊一個字段名稱查看其所有引用。 如果要查看某個字段的實例詳情,右鍵點擊該字段并選擇 Go to Instance,如下圖所示。
3. 在 References 標簽中,如果發現某個引用可能在泄漏內存,則右鍵點擊它并選擇 Go to Instance。
在類列表中,可以查看以下信息:
Heap Count:堆中的實例數。
Shallow Size:此堆中所有實例的總大小(以字節為單位)。
Retained Size:為此類的所有實例而保留的內存總大小(以字節為單位)。
在類列表頂部,可以使用左側下拉列表在以下堆轉儲之間進行切換:
Default heap:系統未指定堆時。
App heap:應用在其中分配內存的主堆。
Image heap:系統啟動映像,包含啟動期間預加載的類。 此處的分配保證絕不會移動或消失。
Zygote heap:寫時復制堆,其中的應用進程是從 Android 系統中派生的。
默認情況下,此列表按 Retained Size 列排序,可以點擊任意列標題以更改列表的排序方式。
在 Instance View 中,每個實例都包含以下信息:
Depth:從任意 GC root 到所選實例的最短 hop 數。
Shallow Size:此實例的大小。
Retained Size:此實例支配的內存大小。
如果要保存堆轉儲以供日后查看,可通過點擊時間線下方工具欄中的 Export heap dump as HPROF file,將堆轉儲導出到一個 HPROF 文件中。 在顯示的對話框中,確保使用 .hprof 后綴保存文件。然后,通過將此文件拖到一個空的編輯器窗口就可以在 Android Studio 中打開該文件。
要使用其他 HPROF 分析器(如 jhat),需要將 HPROF 文件從 Android 格式轉換為 Java SE HPROF 格式。 可以使用 android_sdk/platform-tools/ 目錄中的 hprof-conv 工具執行此操作。 運行該命令需要兩個參數:原始 HPROF 文件和轉換后 HPROF 文件的寫入位置。 例如:
hprof-conv heap-original.hprof heap-converted.hprof
下面舉例說明如何分析內存泄漏。下圖點擊中間“進入內存泄漏Activity”按鈕,可以進入一個產生內存泄漏的Activity,下面展示如何通過Memory Profiler找到產生內存泄漏的類并定位到產生泄漏的代碼。
首先,我們可以反復進入和退出內存泄漏Activity,然后在Memory Profiler中強制垃圾回收,之后捕獲堆轉儲。待分析結果出來后,可以點擊右側的篩選按鈕,篩選我們關注的包名,如下圖所示:
也可以在“Arrange by class”處選擇“Arrange by package”來進行手動選擇。
可以看到有3個MemoryLeakActivity對象,按理說退出它們并GC之后不應該在內存中。我們單擊那一行,右側會顯示每個實例,點擊其中一個實例,下面會顯示其引用,如下圖所示:
我們可以雙擊第一個引用,或者右鍵點擊選擇“Jump to Source”,就可以定位到產生內存泄漏的代碼,如下:
這樣一個內存泄漏的分析過程就結束了。
四. Network Profiler
Network Profiler 能夠在時間線上顯示實時網絡 Activity,包括發送和接收的數據以及當前的連接數,便于查看應用傳輸數據的方式和時間,并據此對底層代碼進行適當優化。
如上圖所示,窗口頂部顯示的是 Event 時間線以及無線裝置功耗狀態(低/高)與 WLAN 的對比①。 在時間線上,可以點擊并拖動選擇時間線的一部分來檢查網絡流量②。 下方的窗口③會顯示在時間線的選定片段內收發的文件,包括文件名稱、大小、類型、狀態和時間。 可以點擊任意列標題排序。 同時,還可以查看時間線選定片段的明細數據,顯示每個文件的發送和接收時間。
點擊網絡連接的名稱即可查看有關所發送或接收的選定文件的詳細信息④。 點擊各個標簽可查看響應數據、標題信息和調用堆棧。
注:Network Profiler 目前只支持 HttpURLConnection 和 OkHttp 網絡連接庫。