Systrace
簡單的性能優化,可能很多人都會。比如以下幾個優化 UI 渲染的方法,想必很多人都知道
使用“設置 --> 開發者選項 --> 調試 GPU 過度繪制”,根據屏幕顯示的不同顏色來區分是存在過度繪制,從而排查該界面的 xml 文件,去除不必要的 background,消除過度繪制
通過 Layout Inspector 查看布局層級,排查是否存在多層無用的嵌套(由于 Hierarchy Viewer 已經被廢棄,如果使用 3.1 及更新版本的 Android Studio,使用 Layout Inspector 查看布局會更加方便)
在 xml 中使用 ViewStub & merge 標簽,優化布局層級
......
上面的這些點當然很重要,但是在某種程度下,上面的這些做法已經力不從心了,我們需要通過其他方式來達到優化性能的目的
俗話說的好,工欲善其事,必先利其器,使用一個好的工具當然可以讓我們事半功倍,由于 TraceView 過于嚴重的運行時開銷,使得 TraceView 測量的很多數據偏差較大,所以 Google 現在強推 systrace,systrace 是一個非常強大的性能分析工具。
systrace 可以從系統層面上,收集并分析設備運行時的所有進程的時間信息,它從 Android 內核中,比如:CPU 調度、磁盤活動和 app 線程中收集信息,然后生成如下圖所示的 html 文件,需要說明的是:生成的 trace.html 文件必須用 Chrome 瀏覽器打開才可以正常的瀏覽使用.
圖片來源:systrace。如上圖所示,‘Frames’ 那一行里面的每一個小圓圈就代表著每一幀,用不同的顏色來代表是否正常的渲染,如果某一個小圓圈用黃色/紅色表示,則表明這一幀的渲染可能存在問題
好,接下來我們來看下如何使用 systrace 工具
二. 如何使用
我使用的 Mac 電腦,所以以下操作都是在 Mac 上進行的,在 Windows 系統上應該也大同小異。
2.1 準備工作
在使用 systrace 之前,需要做以下幾個準備工作
較新的 Android SDK Tools
需要 PC 端配合,PC 端安裝了 Python 且配置在了系統環境變量中
調試的設備需要是 4.3(API Level 18)以上的,系統越高,可以收集到的信息越多,越有利于分析,分析的應用需要是 debug 包
通過 usb 將 Android 設備和 PC 連接成功,處于可調試的狀態
至此,準備工作已完成
2.2 使用
2.2.1 使用方法
通過 Terminal 進入到 /Android/sdk/platform-tools/systrace/ 目錄下:
cd /Users/(個人電腦用戶名)/Library/Android/sdk/platform-tools/systrace
或者直接,不知道systrace的目錄的情況下用以下方法,然后使用命令pwd
,即可知道當前所在的完整路徑:
cd $ANDROID_HOME/platform-tools/systrace
這個時候,在設備上操作應用,使應用進入到待調試的狀態,比如需要調試某個頁面 RecyclerView ,則進入該頁面
然后在 Terminal 里面運行如下命令,其中 [options] [categories]
都是需要輸入的參數:
./systrace.py [options] [categories]
舉例:
調用systrace來記錄10秒鐘內的設備進程,包括圖形進程,并生成mynewtrace.html報告:
python systrace.py --time=10 -o /Users/didi/Downloads/mynewtrace.html gfx
參數信息說明
那么 [options] 和 [categories] 都包括哪些參數呢?
options參數表
options | description |
---|---|
-o < FILE > | 指定輸出的文件,如:-o mynewtrace.html。如果沒有指定此參數,systrace會將您的報告保存到systrace.py所在的同一目錄中,并將其命名為trace.html |
-t N, –time=N | 指定 systrace 的持續時間,如 -t 10,表示記錄 10s 鐘,<T>的單位是 s 秒。如果沒有指定此參數,在按下回車鍵 Enter 健時結束 systrace |
-b N, –buf-size=N | buffer大小(單位kB),用于限制trace總大小,默認無上限 |
-a < APP_NAME >,–app=< APP_NAME > | 指定特定的應用,比如:-a com.lijiankun24.shadowlayout。如果在此應用中使用了 Trace.beginSection("tag") 和 Trace.endSection,默認情況下,這些標簽是不會生效的,除非你通過此命令指定該應用,在 systrace 輸出的 html 文件中才會記錄該標簽標記的方法的信息 |
-h , --help | 顯示幫助信息 |
-l,--list-categories | 列出可用于連接設備的跟蹤categories類別 |
-k functions,--ktrace=functions | 跟蹤中指定的特定內核函數的活動,以逗號分隔的列表 |
-e device-serial,--serial=device-serial | 跟蹤指定的設備序列號標識的特定連接設備 |
catagories參數表
category | description |
---|---|
sched | CPU 的調度信息,可以看到 CPU 的每個核在具體的時間點執行了什么線程 |
gfx | Graphics 渲染系統,包括 SurfaceFlinger、VSync、Texture、RenderThread 的信息 |
input | 輸入事件系統,記錄鍵盤輸入、觸摸等事件信息 |
view | View 視圖系統,常見的 View 的 onMeasure、onLayout、onDraw 都記錄在此系統中 |
webview | WebView |
wm | WindowManager 的調用信息記錄在此模塊中 |
am | ActivityManager 的調用信息記錄在此模塊中 |
sm | Sync Manager |
audio | Audio |
video | Video |
camera | Camera |
hal | Hardware Modules |
app | Application |
res | Resource Loading |
dalvik | 虛擬機相關信息,比如 GC 垃圾回收信息 |
rs | RenderScript |
bionic | Bionic C Library |
power | Power Management |
irq IRQ | Events |
freq | CPU Frequency |
idle | CPU Idle |
disk | Disk I/O |
mmc | eMMC commands |
load | CPU Load |
sync | Synchronization |
workq | Kernel Workqueues |
memreclaim | Kernel Memory Reclaim |
regulators | Voltage and Current Regulators |
生成 trace.html 文件大概就是這樣,并不復雜,下面介紹幾個查看此 html 文件的快捷鍵,通過下面幾個常用的快捷鍵,可以方便的查看 html 文件
快捷鍵
查看Systrace生成的trace.html,瀏覽器打開界面如下:
(圖源來自官網)
顏色塊
每塊顏色占據的長度即為該系統或者自定義trace等執行所占據的時間長度
Alerts
含有三角狀的圓圈圖標,對應出現警告的位置,點擊可以在右邊欄Alerts查看具體警告內容;
警告會告訴你可能丟幀或者卡頓等的原因
Frame
含有F字母的圓圈圖標,對應每一幀開始的位置,不同顏色有不同意義;
綠色表示正常,當顏色為橙色或者紅色時,意味著這一幀超過16.6ms(即發現丟幀);
Kernel
(上圖為四核CPU)顯示每個CPU各自執行的系統方法或自定義trace塊,以及占據的時間長度
SurfaceFlinger
surfaceFilnger,進程id為118,顯示系統方法以及占據的時間長度
com.android.janktown
應用進程,進程id為13409,顯示應用進程內各個線程等信息
每個線程有顏色表示各自不同的狀態
- 灰色:正在休眠。
- 藍色:可運行(它可以運行,但是調度程序尚未選擇讓它運行)。
- 綠色:正在運行(調度程序認為它正在運行)。
- 紅色:不可中斷休眠(通常在內核中處于休眠鎖定狀態)。可以指示 I/O 負載,在調試性能問題時非常有用。
- 橙色:由于 I/O 負載而不可中斷休眠。
分析trace.html圖形信息之前,先了解下快捷鍵
點擊瀏覽器界面上左上角“?”,可以查看到各個快捷鍵提示
快捷鍵 | 作用 |
---|---|
w | 放大時間軸,[+shift]速度更快 |
s | 縮小時間軸,[+shift]速度更快 |
a | 左移時間軸,[+shift]速度更快 |
d | 右移時間軸,[+shift]速度更快 |
f | 放大當前選定區域 |
m | 標記當前選定區域 |
Right Arrow | 選中所選時間軸上的下一個事件 |
Left Arrow | 選中所選時間軸上的上一個事件 |
v | 高亮VSync |
g | 切換是否顯示60hz的網格線 |
0 | 恢復trace到初始態,這里是數字0而非字母o |
h | 切換是否顯示詳情 |
/ | 搜索關鍵字 |
enter | 顯示搜索結果,可通過← →定位搜索結果 |
` | 顯示/隱藏腳本控制臺 |
? | 顯示幫助功能 |
分析trace.html
Systrace可以直觀的看到掉幀引起的界面卡頓
如下圖所示,是一個放大后的 trace.html 的局部圖。我們都知道 Android 系統中的 60 fps 概念,也就是 1s 內會渲染 60 幀,渲染一幀需要 16.6 ms,下圖中用紅色框起來的就是每一個 frame,如果在 16.6 ms 內完成了渲染,則該幀是綠色的,如果渲染超過了 16.6 ms,則呈現出黃色或者紅色
圖片來源 systrace
在上圖中,選中存在問題的黃色幀以后,需要注意兩部分,如下所示
第一個紅色框中,高亮的部分是這一幀在 UI 線程和 RenderThread 線程中都調用了哪些方法
第二個紅色框中,展示了一些信息,包括非常有用的該幀出問題的原因(Alert & Description),這些都是系統給出的存在的問題和優化建議
點擊F,使用快捷鍵f放大該幀,可以選擇m高亮該選區,查看該幀的所有系統trace塊執行時間
查看下面面板的Frame里的信息
ListView recycling takiing too mush time per frame.Ensure your Adapter#getView() binds data efficiently
Alerts選項卡可以查看每個警報以及設備觸發每個警報的次數。如果我們在上圖中,選中右上角的 Alerts tab,會出現如下圖所示的信息,它告訴我們在這段時間內該問題出現的頻次,比如下圖所示的:Inefficient ListView recycling/rebinding
共出現了 55 次。
主要問題是在ListView回收和重新綁定中花費了太多時間。
如果你在UI線程上看到了太多的工作,你需要找出哪些方法消耗了太多的CPU時間。
可以把 Alerts tab 當做一個需要處理的 bug 列表,這個列表中的問題都不同程度上的對我們的幀渲染造成了問題。有時候可能只是幾行代碼的微小改動和優化,卻可以優化我們很多的問題
3.2 為自己的應用添加 Trace 信息
默認情況下,systrace 都只能記錄、收集系統層面的信息,比如 WindowManager
、ActivityManager
、以及 Dalvik 等等模塊的,有沒有什么辦法也記錄收集自己應用中的一些信息呢?
Android 是提供了這樣的 Api 的,這個類是 Trace
類,使用 Trace
類記錄自己應用中的信息其實并不難,如下所示,有如下幾點需要注意
Trace.beginSection(String sectionName)
和Trace.endSection()
需要成對出現,為保證每個Trace.beginSection(String sectionName)
都會有對應的Trace.endSection()
,建議使用try {……} finally {……}
如果在
Trace.endSection()
之前有多個Trace.beginSection(String sectionName)
,Trace.endSection()
會匹配離它最近的一個未匹配過的Trace.beginSection(String sectionName)
Trace.beginSection(String sectionName)
和Trace.endSection()
需要在同一線程中
public class CardViewListActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Trace.beginSection("CardViewListActivity_onCreate");
try {
setContentView(R.layout.activity_card_view_list);
RecyclerView recyclerView = findViewById(R.id.rv_card_view);
recyclerView.setLayoutManager(new LinearLayoutManager(CardViewListActivity.this));
recyclerView.setAdapter(new CardViewListAdapter());
} finally {
Trace.endSection();
}
}
private static class CardViewListAdapter extends RecyclerView.Adapter<CardViewHolder> {
@NonNull
@Override
public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Trace.beginSection("CardViewListAdapter_onCreateViewHolder");
CardViewHolder viewHolder;
try {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view_list, null);
Trace.beginSection("CardViewListAdapter_onCreateViewHolder_newHolder");
try {
viewHolder = new CardViewHolder(view);
} finally {
Trace.endSection();
}
} finally {
Trace.endSection();
}
return viewHolder;
}
}
}
在自己應用的代碼中添加如上代碼之后并沒有結束,還有一點非常重要,在執行 systrace 命令的時候,需要通過 -a <package_name>
指定應用包名,這樣才會記錄、收集到自己應用中添加的 trace 信息,如下所示:
./systrace.py -t 10 -o mytrace.html -a com.lijiankun24.shadowlayout sched freq idle am wm gfx view binder_driver hal dalvik camera input res
在生成的 trace.html 文件中,可以通過右上角的查找,找到 sectionName,就可以查到該 Trace 的記錄信息
3.3 原理淺析
其實 systrace 的思想很簡單,就是在一些關鍵路徑中打 log,通過 log 的開始和結束就可以得到一個方法的執行時間信息,然后將這些 log 收集起來,就可以得到關鍵路徑的運行時間信息,進而得到整個系統的運行性能信息。
在 Android 應用、Android Framework 和 native 層通過不同的方法或類打 log
3.3.1 Android Framework
import android.os.Trace;
Trace.traceBegin(long traceTag, String methodName)
Trace.traceEnd(long traceTag)
比如在 ActivityThread
中的內部類 H.handleMessage(Message msg)
方法如下所示
在 Android Framework 中是通過 Trace.traceBegin(long traceTag, String methodName)
方法打 log 的,傳入的 traceTag 是 Trace
類中的常量類,如下所示
其實這里的 Trace
常量值,和我們在執行 ./systrace [options] [categories]
時,傳入的 [categories]
值對應的
3.3.2 Android 應用
對應的 traceTag 名稱是 TRACE_TAG_APP
,在使用 systrace.py 命令運行時,需要通過 -a <package-name>
指定應用的包名,才可以收集到埋的 tag
import android.os.Trace;
Trace.beginSection(String sectionName)
Trace.EndSection()
Trace
類的源碼如下,可見
traceBegin(long traceTag, String methodName)
、
traceEnd(long traceTag)
、
beginSection(String sectionName)
、
endSection()
最后都調用了 native 方法
nativeTraceBegin(long tag, String name)
和
nativeTraceEnd(long tag)
public final class Trace {
@FastNative
private static native void nativeTraceBegin(long tag, String name);
@FastNative
private static native void nativeTraceEnd(long tag);
private Trace() {
}
public static void traceBegin(long traceTag, String methodName) {
if (isTagEnabled(traceTag)) {
nativeTraceBegin(traceTag, methodName);
}
}
public static void traceEnd(long traceTag) {
if (isTagEnabled(traceTag)) {
nativeTraceEnd(traceTag);
}
}
public static void beginSection(String sectionName) {
if (isTagEnabled(TRACE_TAG_APP)) {
if (sectionName.length() > MAX_SECTION_NAME_LEN) {
throw new IllegalArgumentException("sectionName is too long");
}
nativeTraceBegin(TRACE_TAG_APP, sectionName);
}
}
public static void endSection() {
if (isTagEnabled(TRACE_TAG_APP)) {
nativeTraceEnd(TRACE_TAG_APP);
}
}
}
3.3.3 native 層
其實 systrace 本質上是對其他工具的封裝,包括 PC 端的 atrace
和設備端的 ftrace
,ftrace
是 Linux 內核中的主要跟蹤機制。systrace 使用 atrace
開啟追蹤,然后讀取 ftrace
的緩存,并且把它重新轉換成HTML格式
#include<utils/Trace.h>ATRACE_CALL();
其他
extra:
什么是atrace?什么是ftrace?
ftrace 是一種調試工具,用于了解 Linux 內核中的情況;而 atrace (frameworks/native/cmds/atrace) 使用 ftrace 來捕獲內核事件;
官網簡單的介紹地址:https://source.android.google.cn/devices/tech/debug/ftrace
參考:https://zhuanlan.zhihu.com/p/27331842
轉載于:https://blog.csdn.net/chuyouyinghe/article/details/115657537