一. 概述
性能優(yōu)化是 Android 中的一個重要知識,也是衡量一個 Android 工程師水平的重要依據(jù)琅锻,簡單的性能優(yōu)化,可能很多人都會。比如以下幾個優(yōu)化 UI 渲染的方法型奥,想必很多人都知道
使用“設(shè)置 --> 開發(fā)者選項 --> 調(diào)試 GPU 過度繪制”,根據(jù)屏幕顯示的不同顏色來區(qū)分是存在過度繪制碉京,從而排查該界面的 xml 文件厢汹,去除不必要的 background,消除過度繪制
通過 Layout Inspector 查看布局層級谐宙,排查是否存在多層無用的嵌套(由于 Hierarchy Viewer 已經(jīng)被廢棄烫葬,如果使用 3.1 及更新版本的 Android Studio,使用 Layout Inspector 查看布局會更加方便)
在 xml 中使用 ViewStub & merge 標(biāo)簽凡蜻,優(yōu)化布局層級
......
上面的這些點當(dāng)然很重要搭综,但是在某種程度下,上面的這些做法已經(jīng)力不從心了划栓,我們需要通過其他方式來達(dá)到優(yōu)化性能的目的
俗話說的好兑巾,工欲善其事,必先利其器忠荞,使用一個好的工具當(dāng)然可以讓我們事半功倍蒋歌,由于 TraceView 過于嚴(yán)重的運行時開銷,使得 TraceView 測量的很多數(shù)據(jù)偏差較大委煤,所以 Google 現(xiàn)在強推 systrace堂油,systrace 是一個非常強大的性能分析工具。
systrace 可以從系統(tǒng)層面上碧绞,收集并分析設(shè)備運行時的所有進程的時間信息府框,它從 Android 內(nèi)核中,比如:CPU 調(diào)度讥邻、磁盤活動和 app 線程中收集信息寓免,然后生成如下圖所示的 html 文件,需要說明的是:生成的 trace.html 文件必須用 Chrome 瀏覽器打開才可以正常的瀏覽使用.
圖片來源:systrace计维。如上圖所示袜香,‘Frames’ 那一行里面的每一個小圓圈就代表著每一幀,用不同的顏色來代表是否正常的渲染鲫惶,如果某一個小圓圈用黃色/紅色表示蜈首,則表明這一幀的渲染可能存在問題
好,接下來我們來看下如何使用 systrace 工具
二. 如何使用
我使用的 Mac 電腦,所以以下操作都是在 Mac 上進行的欢策,在 Windows 系統(tǒng)上應(yīng)該也大同小異吆寨。
2.1 準(zhǔn)備工作
在使用 systrace 之前,需要做以下幾個準(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 進入到 /Android/sdk/platform-tools/systrace/ 目錄下
這個時候荣茫,在設(shè)備上操作應(yīng)用,使應(yīng)用進入到待調(diào)試的狀態(tài)场靴,比如需要調(diào)試某個頁面 RecyclerView 啡莉,則進入該頁面
然后在 Terminal 里面運行如下命令,其中 [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
在運行 10s 之后轨帜,就會將記錄的設(shè)備活動生成一個名為 mynewtrace.html 文件魄咕。
2.2.2 參數(shù)說明
那么 [options]
和 [categories]
都包括哪些參數(shù)呢?
[options]
參數(shù)是固定的阵谚,常用的包括以下幾個
options | 縮寫 | 含義 |
---|---|---|
-o <file> | 無 | 指定輸出的文件,如:-o mynewtrace.html 烟具。如果沒有指定此參數(shù)梢什,文件默認(rèn)名稱是 trace.html |
--time=<T> | -t <T> | 指定 systrace 的持續(xù)時間,如 -t 10 朝聋,表示記錄 10s 鐘嗡午,<T>的單位是 s 秒。如果沒有指定此參數(shù)冀痕,在按下回車鍵 Enter 健時結(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)簽是不會生效的吨拗,除非你通過此命令指定該應(yīng)用,在 systrace 輸出的 html 文件中才會記錄該標(biāo)簽標(biāo)記的方法的信息 |
在介紹 [categories]
參數(shù)之前,先介紹 systrace 兩個有用的指令
Global options | 縮寫 | 含義 |
---|---|---|
--help | -h | 查看幫助信息 |
--list-categories | -l | 因為不同的設(shè)備劝篷,Android 系統(tǒng)版本也不一樣哨鸭,支持的 [categories] 參數(shù)也不同〗考耍可以通過此命令查看連接的設(shè)備支持哪些 [categories] 參數(shù) |
比如像鸡,我設(shè)備的 Android 系統(tǒng)是 Android 7.1.2,運行如下命令以后哈恰,可以得到如下圖所示的 [categories]
參數(shù)信息
./systrace -l
上面的這些 [categories]
參數(shù)指明此設(shè)備支持哪些可以被記錄的模塊只估,常用的有以下幾個模塊
categories | 全稱 | 含義 |
---|---|---|
sched | CPU Scheduling | CPU 的調(diào)度信息,可以看到 CPU 的每個核在具體的時間點執(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 | 虛擬機相關(guān)信息幢泼,比如 GC 垃圾回收信息 |
生成 trace.html 文件大概就是這樣紧显,并不復(fù)雜,下面介紹幾個查看此 html 文件的快捷鍵缕棵,通過下面幾個常用的快捷鍵孵班,可以方便的查看 html 文件
快捷鍵 | 含義 |
---|---|
W | 放大時間軸 |
S | 放大時間軸 |
A | 左移時間軸 |
D | 右移時間軸 |
Right Arrow | 選中所選時間軸上的下一個事件 |
Left Arrow | 選中所選時間軸上的上一個事件 |
三. 分析 trace.html
3.1 簡單分析 trace.html 文件
我們該如何分析生成的 trace.html 文件呢?
如下圖所示招驴,是一個放大后的 trace.html 的局部圖篙程。我們都知道 Android 系統(tǒng)中的 60 fps 概念,也就是 1s 內(nèi)會渲染 60 幀别厘,渲染一幀需要 16.6 ms虱饿,下圖中用紅色框起來的就是每一個 frame,如果在 16.6 ms 內(nèi)完成了渲染触趴,則該幀是綠色的氮发,如果渲染超過了 16.6 ms,則呈現(xiàn)出黃色或者紅色
圖片來源 systrace
在上圖中冗懦,選中存在問題的黃色幀以后爽冕,需要注意兩部分,如下所示
第一個紅色框中披蕉,高亮的部分是這一幀在 UI 線程和 RenderThread 線程中都調(diào)用了哪些方法
第二個紅色框中扇售,展示了一些信息前塔,包括非常有用的該幀出問題的原因(Alert & Description),這些都是系統(tǒng)給出的存在的問題和優(yōu)化建議
如果我們在上圖中承冰,選中右上角的 Alerts tab华弓,會出現(xiàn)如下圖所示的信息,它告訴我們在這段時間內(nèi)該問題出現(xiàn)的頻次困乒,比如下圖所示的:Inefficient ListView recycling/rebinding
共出現(xiàn)了 55 次寂屏。
可以把 Alerts tab 當(dāng)做一個需要處理的 bug 列表,這個列表中的問題都不同程度上的對我們的幀渲染造成了問題娜搂。有時候可能只是幾行代碼的微小改動和優(yōu)化迁霎,卻可以優(yōu)化我們很多的問題
3.2 為自己的應(yīng)用添加 Trace 信息
默認(rèn)情況下,systrace 都只能記錄百宇、收集系統(tǒng)層面的信息考廉,比如 WindowManager
、ActivityManager
携御、以及 Dalvik 等等模塊的昌粤,有沒有什么辦法也記錄收集自己應(yīng)用中的一些信息呢?
Android 是提供了這樣的 Api 的啄刹,這個類是 Trace
類涮坐,使用 Trace
類記錄自己應(yīng)用中的信息其實并不難,如下所示誓军,有如下幾點需要注意
Trace.beginSection(String sectionName)
和Trace.endSection()
需要成對出現(xiàn)袱讹,為保證每個Trace.beginSection(String sectionName)
都會有對應(yīng)的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;
}
……
}
……
}
在自己應(yīng)用的代碼中添加如上代碼之后并沒有結(jié)束捷雕,還有一點非常重要,在執(zhí)行 systrace 命令的時候壹甥,需要通過 -a <package_name>
指定應(yīng)用包名救巷,這樣才會記錄、收集到自己應(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 原理淺析
其實 systrace 的思想很簡單,就是在一些關(guān)鍵路徑中打 log淆衷,通過 log 的開始和結(jié)束就可以得到一個方法的執(zhí)行時間信息缸榄,然后將這些 log 收集起來,就可以得到關(guān)鍵路徑的運行時間信息祝拯,進而得到整個系統(tǒng)的運行性能信息甚带。
在 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
類中的常量類鹰贵,如下所示
其實這里的 Trace
常量值晴氨,和我們在執(zhí)行 ./systrace [options] [categories]
時,傳入的 [categories]
值對應(yīng)的
3.3.2 Android 應(yīng)用
對應(yīng)的 traceTag 名稱是 TRACE_TAG_APP
碉输,在使用 systrace.py 命令運行時籽前,需要通過 -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 層
其實 systrace 本質(zhì)上是對其他工具的封裝挠锥,包括 PC 端的 atrace
和設(shè)備端的 ftrace
,ftrace
是 Linux 內(nèi)核中的主要跟蹤機制侨赡。systrace 使用 atrace
開啟追蹤蓖租,然后讀取 ftrace
的緩存,并且把它重新轉(zhuǎn)換成HTML格式
#include<utils/Trace.h>
ATRACE_CALL();
這里介紹了 systrace 基本的分析方法和基本原理辆毡,更深入的分析方法和原理會在后面系列中更新菜秦,敬請期待.
四. 參考
https://developer.android.com/studio/command-line/systrace