Android幀率、卡頓詳解及使用
卡頓分析
FPS幀率統計評測應用流暢度並不準確
系統獲取FPS的原理:手機螢幕顯示的內容是通過Android系統的SurfaceFlinger類,把當前系統裡所有程序需要顯示的資訊合成一幀,然後提交到螢幕上顯示,FPS就是1秒內SurfaceFlinger提交到螢幕的幀數,SurfaceFlinger目前的啟動方式是做為init程序中的一個Service來啟動。
App停止操作後,FPS還是在一直變化,這種情況是否會影響到FPS的準確度?有的時候FPS很低,APP看起來卻很流暢,是因為當前介面在1秒內只需要10幀的顯示需求,當然不會卡頓,此時FPS只要高於10就可以了,如果螢幕根本沒有繪製需求,那FPS的值就是0。
Vsync(垂直同步)指的是顯示卡的輸出幀數和螢幕的垂直重新整理率相同。
垂直同步的含義我們可以理解為,使得顯示卡生成幀的速度和螢幕重新整理的速度的保持一致。舉例來說,如果螢幕的重新整理率為60Hz,那麼生成幀的速度就應該被固定在1/60 s。
Android 4.1引入了VSync機制可以通過其Loop來了解當前App最高繪製能力。
Choreographer接收顯示系統的時間脈衝(垂直同步訊號-VSync訊號),在下一個frame渲染時控制執行這些操作,控制同步處理輸入(Input)、動畫(Animation)、繪製(Draw)三個UI操作。
Choreographer類的構造方法:
1.初始化FrameHandler。接收處理訊息。
2.初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用來接收垂直同步脈衝,就是VSync訊號,VSync訊號是一個時間脈衝,一般為60HZ,用來控制系統同步操作,怎麼同ChoreoGrapher一起工作的,將在下文介紹。
3.初始化mLastFrameTimeNanos(標記上一個frame的渲染時間)以及mFrameIntervalNanos(幀率,fps,一般手機上為1s/60)。
4.初始化CallbackQueue,callback佇列,將在下一幀開始渲染時回撥。
FrameHandler
一個簡單的handler,處理3個型別的訊息。
MSG_DO_FRAME:開始渲染下一幀的操作
MSG_DO_SCHEDULE_VSYNC:請求Vsync訊號
MSG_DO_SCHEDULE_CALLBACK:請求執行callback
FrameDisplayEventReceiver
FrameDisplayEventReceiver繼承自DisplayEventReceiver接收底層的VSync訊號開始處理UI過程。VSync訊號由SurfaceFlinger實現並定時傳送。FrameDisplayEventReceiver收到訊號後,呼叫onVsync方法組織訊息傳送到主執行緒處理。這個訊息主要內容就是run方法裡面的doFrame了,原始碼中mTimestampNanos是訊號到來的時間引數。
1.PostCallBack,發起添加回調,這個FrameCallBack將在下一幀被渲染時執行。
2.AddToCallBackQueue,將FrameCallBack新增到回撥佇列裡面,等待時機執行回撥。每種型別的callback按照設定的執行時間(dueTime)順序排序分別儲存在一個單鏈表中。
3.判斷FrameCallBack設定的執行時間是否在當前時間之後,若是,傳送MSG_DO_SCHEDULE_CALLBACK訊息到主執行緒,安排執行doScheduleCallback,安排執行CallBack。否則直接跳到第4步。
4.執行scheduleFrameLocked,安排執行下一幀。
5.判斷上一幀是否已經執行,若未執行,當前操作直接結束。若已經執行,根據情況執行以下6、7步。
6.若使用垂直同步訊號進行同步,則執行7.否則,直接跳到9。
7.若當前執行緒是UI執行緒,則通過執行scheduleVsyncLocked請求垂直同步訊號。否則,送MSG_DO_SCHEDULE_VSYNC訊息到主執行緒,安排執行doScheduleVsync,在主執行緒呼叫scheduleVsyncLocked。
8.收到垂直同步訊號,呼叫FrameDisplayEventReceiver.onVsync(),傳送訊息到主執行緒,請求執行doFrame。
9.執行doFrame,渲染下一幀。
Choreographer中可以實現FrameCallback介面,然後實現裡邊的doFrame方法,可以獲取到幀率等資訊,通過Choreographer.getInstance().postFrameCallback(new MyFPSFrameCallback());
把你的回撥新增到Choreographer之中,那麼在下一個frame被渲染的時候就會回撥你的callback,執行你定義的doFrame操作,這時候你就可以獲取到這一幀的開始渲染時間並做一些自己想做的事情了。
開源元件Tiny Dancer就是根據這個原理獲取每一幀的渲染時間,繼而分析實現獲取裝置的當前幀率的。有興趣的人可以檢視。
private long mLastFrameTimeNanos=0; //實際執行當前frame的時間
private long mFrameIntervalNanos;
public FpsInfo(long lastFrameTimeNanos){
mLastFrameTimeNanos=lastFrameTimeNanos;
mFrameIntervalNanos=(long)(1000000000 / 60.0); // 幀率,也就是渲染一幀的時間,getRefreshRate是重新整理率,一般是60
}
@Override
public void doFrame(long frameTimeNanos) { //Vsync訊號到來的時間frameTimeNanos
if(mLastFrameTimeNanos==0){
mLastFrameTimeNanos=frameTimeNanos;
}
final long jitterNanos=frameTimeNanos-mLastFrameTimeNanos;
if(jitterNanos>=mFrameIntervalNanos){
final long skippedFrames=jitterNanos/mFrameIntervalNanos;
//Log.i("Testing","Skipped "+skippedFrames+" frames!");
if(skippedFrames>60){ //設定丟幀率 60
Log.i("Testing","Skipped "+skippedFrames+" frames!");
}
}
mLastFrameTimeNanos=frameTimeNanos;
//註冊下一幀的回撥
Choreographer.getInstance().postFrameCallback(this);
}
儘量避免在執行動畫或渲染操作之後在主執行緒執行操作,在之前或之後都應該儘量避免傳送訊息到主執行緒looper。
流暢度衡量指標:
1.幀率FPS
2.丟幀SF(Skipped frame)
3.流暢度SM(SMoothness)。
騰訊bugly對於流暢度及丟幀的連結:http://www.csdn.net/article/2015-06-12/2824949/4
SurfaceFlinger原理及啟動篇:http://gityuan.com/2017/02/11/surface_flinger/
優化流程:
1.UI層級巢狀繪製
2.靜態程式碼掃描工具配合
3.Traceview定位主執行緒卡頓問題
4.Systrace分析
5.Strictmode嚴苛模式主執行緒耗時操作
6.Blockcanary非侵入式自動檢測卡頓