Android性能工具——Systrace使用
一、屏幕刷新機制
基礎(chǔ)概念
在一個典型的顯示系統(tǒng)中蝉揍,一般包括CPU、GPU畦娄、display三個部分又沾, CPU負(fù)責(zé)計算數(shù)據(jù)弊仪,把計算好數(shù)據(jù)交給GPU,GPU會對圖形數(shù)據(jù)進(jìn)行渲染,渲染好后放到buffer里存起來杖刷,然后display(有的文章也叫屏幕或者顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上励饵。
顯示過程,簡單的說就是CPU/GPU準(zhǔn)備好數(shù)據(jù)滑燃,存入buffer役听,display每隔一段時間去buffer里取數(shù)據(jù),然后顯示出來不瓶。display讀取的頻率是固定的禾嫉,比如每個16ms讀一次,但是CPU/GPU寫數(shù)據(jù)是完全無規(guī)律的蚊丐。
簡單的說熙参,屏幕的刷新包括三個步驟:CPU 計算屏幕數(shù)據(jù)、GPU 進(jìn)一步處理和緩存麦备、最后 display 再將緩存中(buffer)的屏幕數(shù)據(jù)顯示出來孽椰。
Screen Tearing(撕裂)
由于display處理的頻率是固定的,而CPU/GPU處理數(shù)據(jù)的時間是不確定的凛篙,因此在早期的設(shè)備上黍匾,由于是單緩沖的模式,則會有屏幕撕裂的情況發(fā)生呛梆。
例如顯示周期為0.01秒锐涯,則在0.01秒時顯示正確,而在0.01秒-0.02秒時填物,CPU/GPU僅完成了部分工作纹腌,則在0.02秒時,屏幕顯示的是上部分為2滞磺,下部分為1的撕裂畫面升薯。
Double-Buffer
雙緩沖技術(shù),基本原理就是采用兩塊buffer击困。一塊back buffer用于CPU/GPU后臺繪制涎劈,另一塊framebuffer則用于顯示,當(dāng)back buffer準(zhǔn)備就緒后阅茶,它們才進(jìn)行交換蛛枚。
為了避免Tearing情況發(fā)生,當(dāng)掃描完一個屏幕后脸哀,設(shè)備需要重新回到第一行以進(jìn)入下一次的循環(huán)坤候,此時有一段時間空隙,這個時間點就是我們進(jìn)行緩沖區(qū)交換的最佳時間企蹭,VSync信號也是在這個時間點產(chǎn)生的白筹。
VSync
在android4.1之前智末,沒有采用Vsync信號時,CPU/GPU往Buffer里面寫數(shù)據(jù)是比較隨意的徒河,CPU/GPU開始工作的時間不是固定的系馆,而Display處理的頻率是固定的,因此顽照,會造成丟幀(Jank)的情況發(fā)生由蘑,如下圖所示。
在android4.1之后代兵,對Android Display系統(tǒng)進(jìn)行了重構(gòu)尼酿,實現(xiàn)了Project Butter,引入了三個核心元素植影,即VSYNC裳擎、Triple Buffer和Choreographer。
Project Butter規(guī)定系統(tǒng)一旦收到vsync通知(16ms觸發(fā)一次),CPU和GPU就立刻開始工作把顯示數(shù)據(jù)寫入buffer思币。
系統(tǒng)規(guī)定收到Vsync信號后鹿响,CPU就開始處理屏幕繪制數(shù)據(jù),CPU/GPU根據(jù)VSYNC信號同步處理數(shù)據(jù)谷饿,可以讓CPU/GPU有完整的16ms時間來處理數(shù)據(jù)惶我,減少了jank。
Triple Buffer
雙緩存的機制并不是完美的博投,比如當(dāng)CPU/GPU工作時間較長時绸贡,會發(fā)生如下情況:
當(dāng)CPU/GPU的處理時間超過16ms時,第一個VSync到來時毅哗,緩沖區(qū)B中的數(shù)據(jù)還沒有準(zhǔn)備好听怕,于是只能繼續(xù)顯示之前A緩沖區(qū)中的內(nèi)容。而B完成后黎做,又因為缺乏VSync信號叉跛,它只能等待下一個信號的來臨松忍。于是在這一過程中蒸殿,有一大段時間是被浪費的。
當(dāng)下一個VSync出現(xiàn)時鸣峭,CPU/GPU馬上執(zhí)行操作宏所,此時它可操作的buffer是A,相應(yīng)的顯示屏對應(yīng)的就是B摊溶。這時看起來就是正常的爬骤。只不過由于執(zhí)行時間仍然超過16ms,導(dǎo)致下一次應(yīng)該執(zhí)行的緩沖區(qū)交換又被推遲了——如此循環(huán)反復(fù)莫换,便出現(xiàn)了越來越多的Jank霞玄。
三緩存不能解決雙緩沖帶來的第一次Jank丟失問題骤铃,但是當(dāng)?shù)谝淮蜼Sync發(fā)生后,CPU不用再等待了坷剧,它會使用第三個buffer C來進(jìn)行下一幀數(shù)據(jù)的準(zhǔn)備工作惰爬。雖然對緩沖區(qū)C的處理所需時間同樣超過了16ms,但這并不影響顯示屏——第2次VSync到來后惫企,它選擇buffer B進(jìn)行顯示;而第3次VSync時撕瞧,它會接著采用C,而不是像double buffering中所看到的情況一樣只能再顯示一遍B了狞尔。這樣子就有效地降低了jank丛版。
二、App相關(guān)的屏幕繪制
App刷新屏幕流程
- 界面上任何一個 View 的刷新請求最終都會走到 ViewRootImpl 中的
scheduleTraversals()
里來安排一次遍歷繪制 View 樹的任務(wù)偏序; -
scheduleTraversals()
會先過濾掉同一幀內(nèi)的重復(fù)調(diào)用页畦,在同一幀內(nèi)只需要安排一次遍歷繪制 View 樹的任務(wù)即可,這個任務(wù)會在下一個屏幕刷新信號到來時調(diào)用performTraversals()
遍歷 View 樹禽车,遍歷過程中會將所有需要刷新的 View 進(jìn)行重繪寇漫; - 接著
scheduleTraversals()
會往主線程的消息隊列中發(fā)送一個同步屏障,攔截這個時刻之后所有的同步消息的執(zhí)行殉摔,但不會攔截異步消息州胳,以此來盡可能的保證當(dāng)接收到屏幕刷新信號時可以盡可能第一時間處理遍歷繪制 View 樹的工作; - 發(fā)完同步屏障后
scheduleTraversals()
才會開始安排一個遍歷繪制 View 樹的操作逸月,作法是把performTraversals()
封裝到 Runnable 里面栓撞,然后調(diào)用 Choreographer 的postCallback()
方法; -
postCallback()
方法會先將這個 Runnable 任務(wù)以當(dāng)前時間戳放進(jìn)一個待執(zhí)行的隊列里碗硬,然后如果當(dāng)前是在主線程就會直接調(diào)用一個native 層方法瓤湘,如果不是在主線程,會發(fā)一個最高優(yōu)先級的 message 到主線程恩尾,讓主線程第一時間調(diào)用這個 native 層的方法弛说; - native 層的這個方法是用來向底層注冊監(jiān)聽下一個屏幕刷新信號,當(dāng)下一個屏幕刷新信號發(fā)出時翰意,底層就會回調(diào) Choreographer 的
onVsync()
方法來通知上層 app木人; -
onVsync()
方法被回調(diào)時,會往主線程的消息隊列中發(fā)送一個執(zhí)行doFrame()
方法的消息冀偶,這個消息是異步消息醒第,所以不會被同步屏障攔截住进鸠; -
doFrame()
方法會去取出之前放進(jìn)待執(zhí)行隊列里的任務(wù)來執(zhí)行稠曼,取出來的這個任務(wù)實際上是 ViewRootImpl 的doTraversal()
操作; - 上述第4步到第8步涉及到的消息都手動設(shè)置成了異步消息客年,所以不會受到同步屏障的攔截霞幅;
-
doTraversal()
方法會先移除主線程的同步屏障漠吻,然后調(diào)用performTraversals()
開始根據(jù)當(dāng)前狀態(tài)判斷是否需要執(zhí)行performMeasure()
測量、perfromLayout()
布局司恳、performDraw()
繪制流程侥猩,在這幾個流程中都會去遍歷 View 樹來刷新需要更新的View;
總結(jié)來說抵赢,當(dāng)有屏幕刷新操作時欺劳,系統(tǒng)會將View樹的測量、布局和繪制等封裝到一個Runnable铅鲤,然后監(jiān)聽VSync信號划提,等Vsync信號來時,再觸發(fā)此Runnable邢享。
三鹏往、卡頓分析利器——Systrace工具
簡介
Systrace是分析Android性能問題的神器,Google IO 2017上更是對其各種強推. 是分析卡頓掉幀問題核心工具骇塘,只要能提供卡頓現(xiàn)場伊履,systrace就能很好定位問題。
原理
在介紹使用之前款违,先簡單說明一下Systrace的原理:它的思想很樸素唐瀑,在系統(tǒng)的一些關(guān)鍵鏈路(比如System Service,虛擬機插爹,Binder驅(qū)動)插入一些信息(我這里稱之為Label)哄辣,通過Label的開始和結(jié)束來確定某個核心過程的執(zhí)行時間,然后把這些Label信息收集起來得到系統(tǒng)關(guān)鍵路徑的運行時間信息赠尾,進(jìn)而得到整個系統(tǒng)的運行性能信息力穗。Android Framework里面一些重要的模塊都插入了Label信息(Java層的通過android.os.Trace類完成,native層通過ATrace宏完成)气嫁,用戶App中可以添加自定義的Label当窗,這樣就組成了一個完成的性能分析系統(tǒng)。另外說明的是:Systrace對系統(tǒng)版本有一個要求寸宵,就是需要Android 4.1以上崖面。系統(tǒng)版本越高叔遂,Android Framework中添加的系統(tǒng)可用Label就越多,能夠支持和分析的系統(tǒng)模塊也就越多彰亥;因此香璃,在可能的情況下,盡可能使用高版本的Android系統(tǒng)來進(jìn)行分析苔货。
獲取systrace文件
要想分析卡頓現(xiàn)場,必須先獲取到卡頓現(xiàn)場的Systrace文件,獲取Systrace文件的方式有兩種救军,一種是通過AndroidSDK/tools
目錄下财异,通過monitor.bat
用Android Device Monitor可視化工具得到,一種是通過python腳本獲取唱遭,我本人更喜歡通過腳本獲取戳寸,因為更方便一點。
1. 通過Android Device Monitor獲取
-
點擊綠色按鈕啟動Systrace
image -
選擇抓取Systrace的配置文件
image
這里拷泽,我們僅僅抓取5秒鐘的系統(tǒng)數(shù)據(jù)疫鹊,沒有選擇特別的應(yīng)用進(jìn)程,抓取的內(nèi)容為基礎(chǔ)內(nèi)容司致。 點擊OK開始抓取
2. 通過python腳本抓炔疬骸(推薦)
裝python2.X版本,Systrace腳本不支持3.X版本脂矫。
通過python腳本執(zhí)行
AndroidSDK\platform-tools\systrace\
目錄下的systrace.py
文件可以配置一些參數(shù)枣耀,類似于通過Android Device Monitor抓取時步驟2配置的顯示信息,若不選擇則默認(rèn)全部抓取庭再。
-
配置一些其他實用參數(shù):
-
-o
: 指定文件輸出位置和文件名 -
-t
: 抓取systrace的時間長度 -
-a
: 指定特殊進(jìn)程包名(自己加Label時必須加上)
-
-
抓取腳本示例:
image
Systrace文件說明
準(zhǔn)備工具
- Chrome瀏覽器(必須)捞奕。
在地址欄輸入chrome://tracing
命令,然后將生成的trace.html
文件拖進(jìn)來拄轻,或者通過load
按鈕導(dǎo)入颅围。
image
常用快捷鍵說明:
-
W
: 放大橫軸,用于查看耗時方法細(xì)節(jié)恨搓; -
S
: 縮小橫軸谷浅,用于查看整體情況; -
A
: 將面板左移奶卓; -
D
: 將面板右移一疯; -
M
: 高亮某一段耗時內(nèi)容。
文件結(jié)構(gòu)
Systrace的文件結(jié)構(gòu)從上到下一般是:
內(nèi)核信息(CPU片信息)夺姑;
SurfaceFlinger(底層繪制信號等信息)墩邀;
system_server等其他進(jìn)程信息。
性能問題分析
一般用Systrace用來分析卡頓盏浙、啟動時間慢等問題眉睹,還可以用來分析方法耗時等。
1. 卡頓問題分析
首先分析一下正常的無卡頓時的表現(xiàn):
當(dāng)按W
并且按M
放大高亮每一幀細(xì)節(jié)的時候废膘,可以看到每一幀的繪制開始都是在16ms以內(nèi)完成的:
從某一幀的刷新竹海,我們也可以知道,刷新是從Choreographer的doFrame()
開始的丐黄,一直到GPU線程繪制完畢結(jié)束斋配。
現(xiàn)在,我們修改代碼,在 ListView 的getView()
方法延遲100ms艰争,然后抓取systrace坏瞄,查看一下耗時情況。
代碼:
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
SystemClock.sleep(100);
if(view != null) {
((TextView)view).setText(list.get(i));
return view;
}
TextView tv = new TextView(viewGroup.getContext());
tv.setText(list.get(i));
return tv;
}
從圖上甩卓,我們可以看見用來標(biāo)識每一幀耗時的F標(biāo)志變成了紅色鸠匀,這說明這一幀是耗時特別嚴(yán)重的。
高亮某一個帶紅色F的幀:
從圖上逾柿,可以看出這一幀耗時達(dá)到548ms缀棍,其中耗時很大的部分為input
,這個input
和上面的deliverInputEvent
相對應(yīng)机错,說明是觸摸事件耗時過長(其實是因為我們在主線程sleep了100ms導(dǎo)致的)睦柴。
為什么這一幀耗時是500ms呢?明明我們只sleep了100ms呀毡熏。原因是我們是在每一個ListView的getView()
方法sleep了100ms坦敌,而這次觸摸事件刷新出了5個item,每一個item的getView()
方法都耗時了100ms(obtainView()
會調(diào)用getView()
)痢法,因此耗時達(dá)到了500多ms狱窘。
分析卡頓的步驟就是:
找到卡頓的場景,并抓取卡頓發(fā)生時的systrace文件财搁;
找到發(fā)生問題的應(yīng)用進(jìn)程的主線程蘸炸,并通過標(biāo)紅的F圖標(biāo),找到發(fā)生問題的問題幀尖奔;
通過放大和高亮去判斷具體是哪一個細(xì)節(jié)點發(fā)生了耗時情況搭儒。
若從systrace文件中看不出來具體是哪一個方法耗時,我們可以自己在代碼中加入Label提茁,去查看方法具體耗時(替代TraceView)淹禾,后面會講到。
2. 啟動時間分析
很多時候茴扁,我們對App的冷啟動時間都有一定的要求铃岔,我們當(dāng)然可以通過adb命令去啟動我們的MainActivity,然后查看啟動時間峭火,但是那并不是真正意義上應(yīng)用完全打開的啟動時間毁习,而通過Systrace則可以很方便的統(tǒng)計啟動時間,并找出耗時的地方卖丸。
這里纺且,我們統(tǒng)計耗時是從用戶點擊Launcher上的應(yīng)用圖標(biāo),到界面完全顯示的時間, 那么界面完全顯示的時間是哪里稍浆?有兩個方法载碌,一個是在onWindowFocusChange()
方法中打印Label猜嘱,或者通過MessageQueue.IdelHandler
來進(jìn)行,這里恐仑,簡單起見我們就用onWindowFocusChange()
方法中加Label吧。
自己加Label主要是通過API:Trace.beginSection("名稱")
和Trace.endSection()
來進(jìn)行为鳄,自己的Label有以下需要注意:
-
begin
和end
必須成對出現(xiàn)裳仆; - Label支持嵌套;
-
begin
和end
必須在同一個線程中孤钦; - 抓取systrace時歧斟,必須指定包名
代碼示例:
private boolean flag = true;
private static Handler mHandler = new Handler();
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(hasFocus && flag) {
flag = false;
mHandler.post(new Runnable() {
@Override
public void run() {
Trace.beginSection("endTime");
SystemClock.sleep(500);
Trace.endSection();
}
});
}
}
systrace文件:
啟動時間計算:
1、找到Launcher進(jìn)程中最后一次手指按下的時間點 deliverInputEvent
的標(biāo)示偏形,這里是起點:
2静袖、終點就是上圖中endTime()
打印時開始的地方,用圖中標(biāo)示的工具俊扭,將兩段時間相連队橙,就能得到本次應(yīng)用的冷啟動時間為434ms。
總結(jié)
屏幕繪制的流程為 CPU計算數(shù)據(jù) -> GPU繪制界面 -> 數(shù)據(jù)放入Buffer -> Display從Buffer中取數(shù)據(jù)呈現(xiàn)萨惑;
Display刷新的時間是固定的(Android中可以理解為16.6ms)捐康,而CPU/GPU計算數(shù)據(jù)的時間不固定,因此會有很多顯示問題(撕裂庸蔼、丟幀)解总;
Systrace可以查看丟幀現(xiàn)場的具體問題點(哪些方法耗時過長導(dǎo)致丟幀);
Systrace可以通過添加Label代替TraceView的作用姐仅,用來統(tǒng)計方法耗時詳情和應(yīng)用啟動時間花枫。