一. 概述
性能優(yōu)化是 Android 中的一個(gè)重要知識(shí),也是衡量一個(gè) Android 工程師水平的重要依據(jù),簡(jiǎn)單的性能優(yōu)化,可能很多人都會(huì)。比如以下幾個(gè)優(yōu)化 UI 渲染的方法,想必很多人都知道
使用“設(shè)置 --> 開發(fā)者選項(xiàng) --> 調(diào)試 GPU 過度繪制”,根據(jù)屏幕顯示的不同顏色來區(qū)分是存在過度繪制,從而排查該界面的 xml 文件,去除不必要的 background,消除過度繪制
通過 Layout Inspector 查看布局層級(jí),排查是否存在多層無用的嵌套(由于 Hierarchy Viewer 已經(jīng)被廢棄,如果使用 3.1 及更新版本的 Android Studio,使用 Layout Inspector 查看布局會(huì)更加方便)
在 xml 中使用 ViewStub & merge 標(biāo)簽,優(yōu)化布局層級(jí)
......
上面的這些點(diǎn)當(dāng)然很重要,但是在某種程度下,上面的這些做法已經(jīng)力不從心了,我們需要通過其他方式來達(dá)到優(yōu)化性能的目的
俗話說的好,工欲善其事,必先利其器,使用一個(gè)好的工具當(dāng)然可以讓我們事半功倍,由于 TraceView 過于嚴(yán)重的運(yùn)行時(shí)開銷,使得 TraceView 測(cè)量的很多數(shù)據(jù)偏差較大,所以 Google 現(xiàn)在強(qiáng)推 systrace,systrace 是一個(gè)非常強(qiáng)大的性能分析工具。
systrace 可以從系統(tǒng)層面上,收集并分析設(shè)備運(yùn)行時(shí)的所有進(jìn)程的時(shí)間信息,它從 Android 內(nèi)核中,比如:CPU 調(diào)度、磁盤活動(dòng)和 app 線程中收集信息,然后生成如下圖所示的 html 文件,需要說明的是:生成的 trace.html 文件必須用 Chrome 瀏覽器打開才可以正常的瀏覽使用.
圖片來源:systrace。如上圖所示,‘Frames’ 那一行里面的每一個(gè)小圓圈就代表著每一幀,用不同的顏色來代表是否正常的渲染,如果某一個(gè)小圓圈用黃色/紅色表示,則表明這一幀的渲染可能存在問題
好,接下來我們來看下如何使用 systrace 工具
二. 如何使用
我使用的 Mac 電腦,所以以下操作都是在 Mac 上進(jìn)行的,在 Windows 系統(tǒng)上應(yīng)該也大同小異。
2.1 準(zhǔn)備工作
在使用 systrace 之前,需要做以下幾個(gè)準(zhǔn)備工作
較新的 Android SDK Tools
需要 PC 端配合,PC 端安裝了 Python 且配置在了系統(tǒng)環(huán)境變量中
調(diào)試的設(shè)備需要是 4.3(API Level 18)以上的,系統(tǒng)越高,可以收集到的信息越多,越有利于分析,分析的應(yīng)用需要是 debug 包
通過 usb 將 Android 設(shè)備和 PC 連接成功,處于可調(diào)試的狀態(tài)
至此,準(zhǔn)備工作已完成
2.2 使用
2.2.1 使用方法
通過 Terminal 進(jìn)入到 /Android/sdk/platform-tools/systrace/ 目錄下
這個(gè)時(shí)候,在設(shè)備上操作應(yīng)用,使應(yīng)用進(jìn)入到待調(diào)試的狀態(tài),比如需要調(diào)試某個(gè)頁(yè)面 RecyclerView ,則進(jìn)入該頁(yè)面
然后在 Terminal 里面運(yùn)行如下命令,其中 [options] [categories]
都是需要輸入的參數(shù)
./systrace.py [options] [categories]
比如,執(zhí)行如下命令,其中 -o mynewtrace.html -t 10
屬于 [options]
參數(shù),sched freq idle am wm gfx view binder_driver hal dalvik camera input res
屬于 [categories]
參數(shù)
./systrace.py -o mynewtrace.html -t 10 sched freq idle am wm gfx view binder_driver hal dalvik camera input res
在運(yùn)行 10s 之后,就會(huì)將記錄的設(shè)備活動(dòng)生成一個(gè)名為 mynewtrace.html 文件。
2.2.2 參數(shù)說明
那么 [options]
和 [categories]
都包括哪些參數(shù)呢?
[options]
參數(shù)是固定的,常用的包括以下幾個(gè)
options | 縮寫 | 含義 |
---|---|---|
-o <file> | 無 | 指定輸出的文件,如:-o mynewtrace.html 。如果沒有指定此參數(shù),文件默認(rèn)名稱是 trace.html |
--time=<T> | -t <T> | 指定 systrace 的持續(xù)時(shí)間,如 -t 10 ,表示記錄 10s 鐘,<T>的單位是 s 秒。如果沒有指定此參數(shù),在按下回車鍵 Enter 健時(shí)結(jié)束 systrace |
--buf-size=<N> | -b <N> | 指定 systrace 的 buffer 是 N kb。指定在 systrace 過程中,收集的數(shù)據(jù)的總?cè)萘?/td> |
--app=<app-name> | -a <app-name> | 指定特定的應(yīng)用,比如:-a com.lijiankun24.shadowlayout 。如果在此應(yīng)用中使用了 Trace.beginSection("tag") 和 Trace.endSection ,默認(rèn)情況下,這些標(biāo)簽是不會(huì)生效的,除非你通過此命令指定該應(yīng)用,在 systrace 輸出的 html 文件中才會(huì)記錄該標(biāo)簽標(biāo)記的方法的信息 |
在介紹 [categories]
參數(shù)之前,先介紹 systrace 兩個(gè)有用的指令
Global options | 縮寫 | 含義 |
---|---|---|
--help | -h | 查看幫助信息 |
--list-categories | -l | 因?yàn)椴煌脑O(shè)備,Android 系統(tǒng)版本也不一樣,支持的 [categories] 參數(shù)也不同。可以通過此命令查看連接的設(shè)備支持哪些 [categories] 參數(shù) |
比如,我設(shè)備的 Android 系統(tǒng)是 Android 7.1.2,運(yùn)行如下命令以后,可以得到如下圖所示的 [categories]
參數(shù)信息
./systrace -l
上面的這些 [categories]
參數(shù)指明此設(shè)備支持哪些可以被記錄的模塊,常用的有以下幾個(gè)模塊
categories | 全稱 | 含義 |
---|---|---|
sched | CPU Scheduling | CPU 的調(diào)度信息,可以看到 CPU 的每個(gè)核在具體的時(shí)間點(diǎn)執(zhí)行了什么線程 |
gfx | Graphics | Graphics 渲染系統(tǒng),包括 SurfaceFlinger、VSync、Texture、RenderThread 的信息 |
input | Input | 輸入事件系統(tǒng),記錄鍵盤輸入、觸摸等事件信息 |
view | View System | View 視圖系統(tǒng),常見的 View 的 onMeasure、onLayout、onDraw 都記錄在此系統(tǒng)中 |
wm | Window Manager | WindowManager 的調(diào)用信息記錄在此模塊中 |
am | Activity Manager | ActivityManager 的調(diào)用信息記錄在此模塊中 |
dalvik | Dalvik VM | 虛擬機(jī)相關(guān)信息,比如 GC 垃圾回收信息 |
生成 trace.html 文件大概就是這樣,并不復(fù)雜,下面介紹幾個(gè)查看此 html 文件的快捷鍵,通過下面幾個(gè)常用的快捷鍵,可以方便的查看 html 文件
快捷鍵 | 含義 |
---|---|
W | 放大時(shí)間軸 |
S | 放大時(shí)間軸 |
A | 左移時(shí)間軸 |
D | 右移時(shí)間軸 |
Right Arrow | 選中所選時(shí)間軸上的下一個(gè)事件 |
Left Arrow | 選中所選時(shí)間軸上的上一個(gè)事件 |
三. 分析 trace.html
3.1 簡(jiǎn)單分析 trace.html 文件
我們?cè)撊绾畏治錾傻?trace.html 文件呢?
如下圖所示,是一個(gè)放大后的 trace.html 的局部圖。我們都知道 Android 系統(tǒng)中的 60 fps 概念,也就是 1s 內(nèi)會(huì)渲染 60 幀,渲染一幀需要 16.6 ms,下圖中用紅色框起來的就是每一個(gè) frame,如果在 16.6 ms 內(nèi)完成了渲染,則該幀是綠色的,如果渲染超過了 16.6 ms,則呈現(xiàn)出黃色或者紅色
圖片來源 systrace
在上圖中,選中存在問題的黃色幀以后,需要注意兩部分,如下所示
第一個(gè)紅色框中,高亮的部分是這一幀在 UI 線程和 RenderThread 線程中都調(diào)用了哪些方法
第二個(gè)紅色框中,展示了一些信息,包括非常有用的該幀出問題的原因(Alert & Description),這些都是系統(tǒng)給出的存在的問題和優(yōu)化建議
如果我們?cè)谏蠄D中,選中右上角的 Alerts tab,會(huì)出現(xiàn)如下圖所示的信息,它告訴我們?cè)谶@段時(shí)間內(nèi)該問題出現(xiàn)的頻次,比如下圖所示的:Inefficient ListView recycling/rebinding
共出現(xiàn)了 55 次。
可以把 Alerts tab 當(dāng)做一個(gè)需要處理的 bug 列表,這個(gè)列表中的問題都不同程度上的對(duì)我們的幀渲染造成了問題。有時(shí)候可能只是幾行代碼的微小改動(dòng)和優(yōu)化,卻可以優(yōu)化我們很多的問題
3.2 為自己的應(yīng)用添加 Trace 信息
默認(rèn)情況下,systrace 都只能記錄、收集系統(tǒng)層面的信息,比如 WindowManager
、ActivityManager
、以及 Dalvik 等等模塊的,有沒有什么辦法也記錄收集自己應(yīng)用中的一些信息呢?
Android 是提供了這樣的 Api 的,這個(gè)類是 Trace
類,使用 Trace
類記錄自己應(yīng)用中的信息其實(shí)并不難,如下所示,有如下幾點(diǎn)需要注意
Trace.beginSection(String sectionName)
和Trace.endSection()
需要成對(duì)出現(xiàn),為保證每個(gè)Trace.beginSection(String sectionName)
都會(huì)有對(duì)應(yīng)的Trace.endSection()
,建議使用try {……} finally {……}
如果在
Trace.endSection()
之前有多個(gè)Trace.beginSection(String sectionName)
,Trace.endSection()
會(huì)匹配離它最近的一個(gè)未匹配過的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;
}
……
}
……
}
在自己應(yīng)用的代碼中添加如上代碼之后并沒有結(jié)束,還有一點(diǎn)非常重要,在執(zhí)行 systrace 命令的時(shí)候,需要通過 -a <package_name>
指定應(yīng)用包名,這樣才會(huì)記錄、收集到自己應(yīng)用中添加的 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 原理淺析
其實(shí) systrace 的思想很簡(jiǎn)單,就是在一些關(guān)鍵路徑中打 log,通過 log 的開始和結(jié)束就可以得到一個(gè)方法的執(zhí)行時(shí)間信息,然后將這些 log 收集起來,就可以得到關(guān)鍵路徑的運(yùn)行時(shí)間信息,進(jìn)而得到整個(gè)系統(tǒng)的運(yùn)行性能信息。
在 Android 應(yīng)用、Android Framework 和 native 層通過不同的方法或類打 log
3.3.1 Android Framework
import android.os.Trace;
Trace.traceBegin(long traceTag, String methodName)
Trace.traceEnd(long traceTag)
比如在 ActivityThread
中的內(nèi)部類 H.handleMessage(Message msg)
方法如下所示
在 Android Framework 中是通過 Trace.traceBegin(long traceTag, String methodName)
方法打 log 的,傳入的 traceTag 是 Trace
類中的常量類,如下所示
其實(shí)這里的 Trace
常量值,和我們?cè)趫?zhí)行 ./systrace [options] [categories]
時(shí),傳入的 [categories]
值對(duì)應(yīng)的
3.3.2 Android 應(yīng)用
對(duì)應(yīng)的 traceTag 名稱是 TRACE_TAG_APP
,在使用 systrace.py 命令運(yùn)行時(shí),需要通過 -a <package-name>
指定應(yīng)用的包名,才可以收集到埋的 tag
import android.os.Trace;
Trace.beginSection(String sectionName)
Trace.EndSection()
Trace
類的源碼如下,可見 traceBegin(long traceTag, String methodName)
、traceEnd(long traceTag)
、beginSection(String sectionName)
、endSection()
最后都調(diào)用了 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 層
其實(shí) systrace 本質(zhì)上是對(duì)其他工具的封裝,包括 PC 端的 atrace
和設(shè)備端的 ftrace
,ftrace
是 Linux 內(nèi)核中的主要跟蹤機(jī)制。systrace 使用 atrace
開啟追蹤,然后讀取 ftrace
的緩存,并且把它重新轉(zhuǎn)換成HTML格式
#include<utils/Trace.h>
ATRACE_CALL();
這里介紹了 systrace 基本的分析方法和基本原理,更深入的分析方法和原理會(huì)在后面系列中更新,敬請(qǐng)期待.
四. 參考
https://developer.android.com/studio/command-line/systrace