前言
編寫本文時(shí)使用的操作系統(tǒng)環(huán)境為 Ubuntu 20.04.3 LTS ,使用的 Android Studio 為最新版的 Arctic Fox量没。
1. Systrace 簡(jiǎn)介
1.1 簡(jiǎn)介
Systrace 就是 System Trace 的簡(jiǎn)稱玉转,可以記錄一段時(shí)間內(nèi)系統(tǒng)的活動(dòng)狀態(tài),并可以生成HTML格式的報(bào)告殴蹄。開發(fā)者可以通過分析這份報(bào)告究抓,發(fā)現(xiàn)應(yīng)用和系統(tǒng)的性能瓶頸。
1.2 原理
Systrace 的原理很簡(jiǎn)單袭灯,Google 的開發(fā)人員在 Android 系統(tǒng)的一些關(guān)鍵組件(System Server漩蟆、虛擬機(jī)等)已經(jīng)預(yù)先做了埋點(diǎn),Java 層使用 TraceCompat 進(jìn)行埋點(diǎn)妓蛮, Native 層可以使用使用 ATrace 怠李。通過這些埋點(diǎn)就可以得到核心方法的執(zhí)行開始和結(jié)束時(shí)間。
2. 啟動(dòng) Systrace
在 Systrace 推出之初有以下兩種啟動(dòng)方式:
2.1 使用 Android Device Monitor 啟動(dòng) Systrace (過時(shí))
在以往的Android Studio 版本中我們可以通過Android Device Monitor 來捕獲System trace蛤克。但是這種方式已經(jīng)過時(shí)了捺癞,捕獲的Trace.html在新版的Chrome中打開會(huì)出現(xiàn)以下錯(cuò)誤。
想搞明白什么會(huì)有這樣的錯(cuò)誤构挤,可以參考這里的回答:https://stackoverflow.com/questions/36865899/react-native-android-systrace-html-is-blank-in-ubuntu-14-04
2.2 使用 Python 命令啟動(dòng) Systrace (推薦)
使用Python 命令執(zhí)行Systrace 共分三步髓介。
- 從 Android Studio 下載并安裝最新的 Android SDK 工具
2.安裝 Python2 環(huán)境,Systrace 不支持 Python3
3.使用將 Android 4.3(API 級(jí)別 18)或更高版本的設(shè)備連接到電腦
4.在控制臺(tái)中執(zhí)行 Systrace 指令(下一節(jié)具體介紹)
systrace
命令在 Android SDK 工具軟件包中提供筋现,并且可以在 android/Sdk/platform-tools/systrace/
中找到唐础。
-
Systrace 指令
Sysrace 在命令行中的語法結(jié)構(gòu)如下
python systrace.py [options] [categories]
例如,以下命令會(huì)調(diào)用 systrace
來記錄設(shè)備活動(dòng)矾飞,并生成一個(gè)名為 mynewtrace.html
的 HTML 報(bào)告一膨。此類別列表是大多數(shù)設(shè)備的合理默認(rèn)列表。
# 使用前需要先cd到systrace.py所在的目錄
python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \
binder_driver hal dalvik camera input res
提示:如果要在跟蹤輸出中查看任務(wù)名稱洒沦,必須在命令參數(shù)中添加
sched
類別咐低。
3.1 Systrace 支持的 options
在控制臺(tái)輸入python systrace.py -h
可以查看Systrace
支持的所有options
以及對(duì)應(yīng)的作用搓幌。st
是python systrace.py
的別名,需要預(yù)先在環(huán)境變量中定義膛锭,之后會(huì)介紹卢未。
3.1.1 systrce -o
將 HTML 跟蹤報(bào)告寫入指定的文件携取。如果未指定此選項(xiàng)稚疹,systrace
會(huì)將報(bào)告保存到 systrace.py
所在的目錄中香拉,并將其命名為 trace.html
。'
python systrace.py -o trace.html
3.1.2 systrce -t N
跟蹤設(shè)備活動(dòng) N 秒濒翻。如果未指定此選項(xiàng)屁柏,systrace
會(huì)提示在命令行中按 Enter 鍵結(jié)束跟蹤.
python systrace.py -t 5
3.1.3 systrce -b N
使用 N KB 的跟蹤緩沖區(qū)大小啦膜。使用此選項(xiàng),可以限制跟蹤期間收集到的數(shù)據(jù)的總大小前联。
python systrace.py -b 2048
3.1.4 systrce -k functions
跟蹤逗號(hào)分隔列表中指定的特定內(nèi)核函數(shù)的活動(dòng)。
python systrace.py -k
3.1.5 systrce -a app-name
啟用對(duì)應(yīng)用的跟蹤娶眷,指定為包含進(jìn)程名稱的逗號(hào)分隔列表似嗤。這些應(yīng)用必須包含 Trace
類中的跟蹤檢測(cè)調(diào)用。應(yīng)在分析應(yīng)用時(shí)指定此選項(xiàng)届宠。很多庫(例如 RecyclerView
)都包括跟蹤檢測(cè)用烁落,這些調(diào)用可在啟用應(yīng)用級(jí)跟蹤時(shí)提供有用的信息。如需了解詳情豌注,請(qǐng)參閱定義自定義事件伤塌。如需跟蹤搭載 Android 9(API 級(jí)別 28)或更高版本的設(shè)備上的所有應(yīng)用,請(qǐng)傳遞用添加引號(hào)的通配符字符 "*"
轧铁。
python systrace.py -a
# 或
python systrace.py -a "*"
3.1.6 systrce --from-file=file-path
根據(jù)文件(例如包含原始跟蹤數(shù)據(jù)的 TXT 文件)創(chuàng)建交互式 HTML 報(bào)告每聪。
python systrace.py --form-file = trace.txt
3.1.7 systrce -e device-serial
在已連接的特定設(shè)備(由對(duì)應(yīng)的設(shè)備序列號(hào)標(biāo)識(shí))上進(jìn)行跟蹤。
python systrace.py -e emulator-5554
設(shè)備序列號(hào)可以使用 adb devices 查看
3.2 Systrace 支持的 categories
使用 -l
指令可以查已連接設(shè)備可用的服務(wù)列表齿风,如下所示药薯,部分服務(wù)只能在取得root權(quán)限時(shí)才能追蹤。
3.3 快捷啟動(dòng)Systrace
在Ubuntu系統(tǒng)的home目錄中打開.bashrc文件救斑,在最底部加入以下代碼童本,
# Systrace
alias st='python /home/link/Android/Sdk/platform-tools/systrace/systrace.py'
alias st-start='python /home/link/Android/Sdk/platform-tools/systrace/systrace.py -o /home/link/桌面/TraceReport/trace.html'
然后命令行中執(zhí)行 source .bashrc,使配置生效脸候。
source .bashrc
這樣我們就可以使用 st
和 st-start
來快速執(zhí)行 systrace
腳本穷娱。
4. 瀏覽 Systrace 報(bào)告
以下內(nèi)容部分摘錄自Android官方文檔 - 瀏覽Systrace報(bào)告 | Android開發(fā)者,為了便于閱讀运沦,在官方文檔的基礎(chǔ)上有一定修改和刪減泵额。
在Chrome瀏覽器(或是基于Chromium的Edge瀏覽器)的地址欄輸入chrome://tracing
命令,然后將生成的trace.html
文件拖進(jìn)來携添,或者通過load
按鈕導(dǎo)入梯刚,可以看到如下所示的報(bào)告:
Systrace
會(huì)生成包含多個(gè)部分的輸出 HTML 文件。該報(bào)告列出了每個(gè)進(jìn)程的線程薪寓。如果給定線程會(huì)渲染界面幀亡资,該報(bào)告還會(huì)沿時(shí)間軸指明所渲染的幀。將報(bào)告從左向右移動(dòng)時(shí)向叉,時(shí)間會(huì)向前推移锥腻。報(bào)告從上到下包含以下幾個(gè)部分:
4.1 用戶互動(dòng)
第一部分包含表示應(yīng)用或游戲中的具體用戶互動(dòng)(例如點(diǎn)按設(shè)備屏幕)的條形圖。這些互動(dòng)可用作有用的時(shí)間標(biāo)記母谎。
4.2 CPU 活動(dòng)
下一部分顯示了表示每個(gè) CPU 中的線程活動(dòng)的條形圖瘦黑。這些條形會(huì)顯示所有應(yīng)用(包括應(yīng)用或游戲)中的 CPU 活動(dòng)。
CPU 活動(dòng)部分可以展開,展開后就可以查看每個(gè) CPU 的休眠狀態(tài)幸斥,C-State可以簡(jiǎn)單看做CPU的工作狀態(tài)匹摇。更進(jìn)一步的含義,可以參考這篇文章:https://www.cnblogs.com/apnpc/p/13780146.html
4.3 系統(tǒng)事件
此部分中的直方圖會(huì)顯示特定的系統(tǒng)級(jí)事件甲葬,例如特定對(duì)象的紋理計(jì)數(shù)和總大小廊勃。
值得仔細(xì)檢查的直方圖是標(biāo)記為 SurfaceView 的直方圖。計(jì)數(shù)表示已傳遞到顯示管道并等待顯示在設(shè)備屏幕上的組合幀緩沖區(qū)的數(shù)量经窖。由于大多數(shù)設(shè)備都會(huì)進(jìn)行雙重或三重緩沖坡垫,因此該計(jì)數(shù)幾乎總為 0、1 或 2画侣。
描繪 Surface Flinger 進(jìn)程(包括 VSync 事件和界面線程交換工作)的其他直方圖冰悠,如圖所示:
4.4 顯示幀
這一部分通常是報(bào)告中最頂部的部分,描繪了一條多色線條配乱,后面是成堆的條形溉卓。這些形狀表示已創(chuàng)建的特定線程的狀態(tài)和幀堆棧。堆棧的每個(gè)層級(jí)代表對(duì) beginSection()
的一次調(diào)用搬泥,或?yàn)閼?yīng)用或游戲定義的自定義跟蹤事件的開頭的诵。
注意:界面線程(即通常運(yùn)行應(yīng)用或游戲的主線程)始終會(huì)顯示為第一個(gè)線程。
每個(gè)條形堆上方的多色線條表示特定線程隨時(shí)間變化的一組狀態(tài)佑钾。每段線條可以包含以下顏色之一:
綠色:正在運(yùn)行
線程正在完成與某個(gè)進(jìn)程相關(guān)的工作或正在響應(yīng)中斷西疤。
藍(lán)色:可運(yùn)行
線程可以運(yùn)行但目前未進(jìn)行調(diào)度。
白色:休眠
線程沒有可執(zhí)行的任務(wù)休溶,可能是因?yàn)榫€程在遇到斥鎖定時(shí)被阻止代赁。
橙色:不可中斷的休眠
線程在遇到 I/O 操作時(shí)被阻止或正在等待磁盤操作完成。
紫色:可中斷的休眠
線程在遇到另一項(xiàng)內(nèi)核操作(通常是內(nèi)存管理)時(shí)被阻止兽掰。
注意:在 Systrace 報(bào)告中芭碍,可以點(diǎn)擊該線條以確定該線程在給定時(shí)間由哪個(gè) CPU 控制。
4.4 鍵盤快捷鍵
下表列出了查看 Systrace
報(bào)告時(shí)可以使用的鍵盤快捷鍵:
快捷鍵 | 說明 |
---|---|
W | 放大跟蹤時(shí)間軸孽尽。 |
A | 在跟蹤時(shí)間軸上向左平移窖壕。 |
S | 縮小跟蹤時(shí)間軸。 |
D | 在跟蹤時(shí)間軸上向右平移杉女。 |
E | 以當(dāng)前鼠標(biāo)位置為中定位跟蹤時(shí)間軸瞻讽。 |
M | 高亮當(dāng)前選區(qū)。 |
1 | 將當(dāng)前正在使用中的選擇模型更改為“選擇”模式熏挎。對(duì)應(yīng)于鼠標(biāo)選擇器工具欄中顯示的第 1 個(gè)按鈕 |
2 | 將當(dāng)前正在使用中的選擇模型更改為“平移”模式速勇。對(duì)應(yīng)于鼠標(biāo)選擇器工具欄中顯示的第 2 個(gè)按鈕 |
3 | 將當(dāng)前正在使用中的選擇模型更改為“縮放”模式。對(duì)應(yīng)于鼠標(biāo)選擇器工具欄中顯示的第 3 個(gè)按鈕 |
4 | 將當(dāng)前正在使用中的選擇模型更改為“計(jì)時(shí)”模式坎拐。對(duì)應(yīng)于鼠標(biāo)選擇器工具欄中顯示的第 4 個(gè)按鈕 |
G | 在當(dāng)前所選任務(wù)的開頭顯示網(wǎng)格烦磁。 |
Shift + G | 在當(dāng)前所選任務(wù)的末尾顯示網(wǎng)格养匈。 |
向左箭頭 | 在當(dāng)前選定的時(shí)間軸上選擇上一個(gè)事件。 |
向右箭頭 | 在當(dāng)前選定的時(shí)間軸上選擇下一個(gè)事件都伪。 |
4.5 調(diào)查性能問題
與 Systrace 報(bào)告互動(dòng)時(shí)呕乎,可以檢查記錄期間的設(shè)備 CPU 使用情況。如需瀏覽 HTML 報(bào)告方面的幫助陨晶,請(qǐng)查看鍵盤快捷鍵部分猬仁,或點(diǎn)擊報(bào)告右上角的 ? 按鈕。
以下各部分介紹了如何檢查報(bào)告中的信息以查找和修復(fù)性能問題珍逸。
4.6 識(shí)別性能問題
瀏覽 Systrace 報(bào)告時(shí)逐虚,可以通過執(zhí)行以下一項(xiàng)或多項(xiàng)操作來更輕松地識(shí)別性能問題:
通過在時(shí)間間隔周圍繪制一個(gè)矩形來選擇所需的時(shí)間間隔聋溜。
使用標(biāo)尺工具標(biāo)記或突出顯示問題區(qū)域谆膳。
依次點(diǎn)擊 View Options > Highlight VSync,以顯示每項(xiàng)顯示屏刷新操作撮躁。
4.7 檢查界面幀和提醒
注意:本節(jié)中的內(nèi)容只面向托管代碼漱病,因?yàn)?Systrace 是通過查看系統(tǒng)基于 Java 的 Choreographer 來提供幀信息。如需面向原生代碼(特別是游戲)的指導(dǎo)把曼,請(qǐng)參閱關(guān)于幀速率一致性的討論杨帽。
如所示,Systrace 報(bào)告列出了渲染界面幀的每個(gè)進(jìn)程嗤军,并指明了沿時(shí)間軸渲染的每個(gè)幀注盈。在 16.6 毫秒內(nèi)渲染的必須保持每秒 60 幀穩(wěn)定幀速率的幀會(huì)以綠色圓圈表示。渲染時(shí)間超過 16.6 毫秒的幀會(huì)以黃色或紅色幀圓圈表示
注意:在搭載 Android 5.0(API 級(jí)別 21)或更高版本的設(shè)備上叙赚,渲染幀的工作拆分為界面線程和渲染線程老客。在以前的版本中,創(chuàng)建幀的所有工作都是在界面線程中完成的震叮。
點(diǎn)擊某個(gè)幀圓圈可將其突出顯示胧砰,并提供有關(guān)系統(tǒng)為渲染該幀所做工作的其他信息,包括提醒苇瓣。此報(bào)告還會(huì)顯示系統(tǒng)在渲染該幀時(shí)執(zhí)行的方法尉间。可以調(diào)查這些方法以確定界面卡頓的可能原因击罪。
選擇運(yùn)行速度慢的幀后哲嘲,可能會(huì)在報(bào)告的底部窗格中看到一條提醒。圖中顯示的提醒指明幀的主要問題是在[ListView]
回收和重新綁定上花費(fèi)了太多時(shí)間媳禁。指向跟蹤記錄中相關(guān)事件的鏈接可詳細(xì)說明系統(tǒng)在此期間執(zhí)行的操作撤蚊。
如需查看此工具在的跟蹤記錄中發(fā)現(xiàn)的每條提醒以及設(shè)備觸發(fā)每條提醒的次數(shù),請(qǐng)點(diǎn)擊窗口最右側(cè)的 Alerts 標(biāo)簽頁损话,如圖所示侦啸。Alerts 面板可幫助了解跟蹤記錄中出現(xiàn)的問題以及這些問題導(dǎo)致出現(xiàn)卡頓的頻率槽唾。可以將此面板視為要修正的錯(cuò)誤列表光涂。通常情況下庞萍,只需對(duì)一個(gè)區(qū)域進(jìn)行細(xì)微改動(dòng)或改進(jìn)即可移除整組提醒。
如果發(fā)現(xiàn)在界面線程上執(zhí)行的工作太多忘闻,請(qǐng)使用以下方法之一來幫助確定哪些方法占用了過多的 CPU 時(shí)間:
如果想了解哪些方法可能會(huì)導(dǎo)致瓶頸钝计,請(qǐng)?jiān)谶@些方法中使用TraceCompat添加跟蹤標(biāo)記。具體的使用方式會(huì)在5.使用Systrace實(shí)戰(zhàn) 中介紹齐佳。
如果不確定界面瓶頸的來源私恬,請(qǐng)使用 Android Studio 中提供的CPU profiler×段猓可以生成跟蹤日志本鸣,然后使用 CPU 分析器導(dǎo)入和檢查這些日志。
CPU Profiler 的使用方式可以參考這篇文章硅蹦,APP 性能優(yōu)化與分析CPU篇(一)-CPU Profiler
5. Systrace 實(shí)戰(zhàn)
5.1 應(yīng)用啟動(dòng)耗時(shí)分析
App冷啟動(dòng)耗時(shí)是衡量應(yīng)用啟動(dòng)性能的一個(gè)重要指標(biāo)荣德,獲取App啟動(dòng)耗時(shí)一般分為
- adb命令獲取
可以獲取到一個(gè)不足夠嚴(yán)謹(jǐn)?shù)膯?dòng)時(shí)間,大部分情況下夠用童芹,但是只能在線下使用涮瞻。
- 手動(dòng)打點(diǎn)
需要自行應(yīng)用中埋點(diǎn),相對(duì)繁瑣假褪,但是時(shí)間足夠精確署咽,線上、線下都可以使用生音。
- 利用高速攝影機(jī)宁否,逐幀計(jì)算
需要自己逐幀計(jì)算,而且獲取到的時(shí)間也不是十分精確久锥。但是使用非常方便家淤,只需要一部支持延遲攝影功能的手機(jī)即可(例如iphone),適用于手邊沒有電腦的時(shí)候計(jì)算啟動(dòng)耗時(shí)瑟由。
這里演示在應(yīng)用中埋點(diǎn)絮重,并利用Systrace
計(jì)算啟動(dòng)耗時(shí)。在應(yīng)用中埋點(diǎn)歹苦,主要基于TraceCompat類來實(shí)現(xiàn)青伤。TraceCompat API 如下:
方法名 | 作用 |
---|---|
static void beginAsyncSection(String methodName, int cookie) | 開始異步追蹤。之后必須使用相同的methodName和cookie調(diào)用endAsyncSection(String,int)殴瘦,結(jié)束這個(gè)追蹤狠角。與beginSection(String)和endSection()不同,異步事件不需要嵌套蚪腋。 |
static void beginSection(String sectionName) | 開始追蹤丰歌。之后必須在同一線程中調(diào)用endSection()結(jié)束追蹤姨蟋。 |
static void endAsyncSection(String methodName, int cookie) | 結(jié)束異步追蹤。 |
static void endSection() | 結(jié)束追蹤立帖。 |
static boolean isEnabled() | 檢查當(dāng)前是否啟用了跟蹤眼溶。這有助于避免為需要格式化的跟蹤節(jié)創(chuàng)建中間字符串。沒有必要保護(hù)所有跟蹤方法調(diào)用晓勇,因?yàn)樗鼈冊(cè)趦?nèi)部已經(jīng)檢查了這一點(diǎn)堂飞。但是,建議使用此選項(xiàng)來防止創(chuàng)建任何臨時(shí)對(duì)象绑咱,這些臨時(shí)對(duì)象在未啟用跟蹤時(shí)會(huì)傳遞給這些方法以降低運(yùn)行時(shí)成本绰筛。 |
static void setCounter(String counterName, int counterValue) | 寫入跟蹤消息以指示給定計(jì)數(shù)器的值。 |
- 在應(yīng)用代碼中設(shè)置埋點(diǎn)描融。
public class JniApp extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// APP 啟動(dòng) 埋點(diǎn)
TraceCompat.beginAsyncSection("JNI-app-start",10);
}
}
public class MainActivity extends AppCompatActivity {
...
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// MainActivity 首幀繪制結(jié)束后添加埋點(diǎn)
TraceCompat.endAsyncSection("JNI-app-start",10);
}
...
}
注意:認(rèn)為Activity的首幀繪制完畢就是APP啟動(dòng)完畢的情況僅限于首頁相對(duì)簡(jiǎn)單的情況铝噩,多數(shù)情況也確實(shí)如此。但是如果開屏頁很復(fù)雜稼稿,應(yīng)該將開屏頁最后一個(gè)組件繪制結(jié)束作為應(yīng)用啟動(dòng)完畢的信號(hào)薄榛。示例如下:
xxxView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
TraceCompat.endAsyncSection("JNI-app-start",10);
return true;
}
});
- 在命令行運(yùn)行
Systrace
指令讳窟,并指定應(yīng)用的包名
st -a com.android.car.demo
得到如下的報(bào)告
- 選擇應(yīng)用的包名让歼,過濾掉不需要的進(jìn)程。
- 檢索我們?cè)O(shè)定的標(biāo)簽丽啡,點(diǎn)擊 報(bào)告中
->
切換到自定義事件谋右。
- 按下
m
鍵,鎖定事件补箍。
從上圖就可以看出改执,整個(gè)JNI-app-start
事件總共耗時(shí) 2.07s,而這就是應(yīng)用的啟動(dòng)耗時(shí)坑雅。
如果想進(jìn)一步分析為何啟動(dòng)耗時(shí)如此之長(zhǎng)辈挂,需要繼續(xù)在可疑的地方埋點(diǎn),逐一排查裹粤。如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 第一處 可疑點(diǎn)终蒂,布局加載耗時(shí)過長(zhǎng)?
TraceCompat.beginSection("JNI-layout");
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TraceCompat.endSection();
// 第二處 可疑點(diǎn)遥诉,初始化耗時(shí)過長(zhǎng)拇泣?
TraceCompat.beginSection("JNI-init");
initSomeThing();
TraceCompat.endSection();
TextView tv = binding.sampleText;
// 第三處 可疑點(diǎn),JNI方法返回值耗時(shí)過長(zhǎng)矮锈?
TraceCompat.beginSection("JNI-txt");
String txt = stringFromJNI();
TraceCompat.endSection();
tv.setText(txt);
}