從FrameCallback理解Choreographer原理及簡單幀率監控應用
簡單來說,Choreographer主要作用是協調動畫,輸入和繪製的時間,它從顯示子系統接收定時脈衝(例如垂直同步),然後安排渲染下一個frame的一部分工作。
自定義FrameCallback
FrameCallback是和Choreographer互動,在下一個frame被渲染時觸發的介面類。開發者可以設定自己的FrameCallback。我們就從自定義FrameCallback作為切入口,嘗試窺探一下Choreographer的實現原理。簡單實現如下:
private static final String TAG = "Choreographer_test";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ImageView imageView= (ImageView) findViewById(R.id.iv_anim);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final long starTime=System.nanoTime();
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
Log.e(TAG,"starTime=" +starTime+", frameTimeNanos="+frameTimeNanos+", frameDueTime="+(frameTimeNanos-starTime)/1000000);
}
});
}
});
}
在這裡,我們自定義的FrameCallback只是簡單把時間列印了一下。輸出如下資訊:
E/Choreographer_test: starTime=232157742945242, frameTimeNanos=232157744964255, frameDueTime=2
從log可以看出,這一幀大概2ms就處理完畢。下面我們從原始碼角度窺探一下它具體的實現原理。
實現原理
1. 關鍵成員變數
建構函式
private Choreographer(Looper looper) {
mLooper = looper;
//1.建立Handler物件,用於處理訊息
mHandler = new FrameHandler(looper);
//2.建立接收VSYNC訊號的物件
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
//3.初始化上一次frame渲染的時間點
mLastFrameTimeNanos = Long.MIN_VALUE;
//4.幀率,也就是渲染一幀的時間,getRefreshRate是重新整理率,一般是60
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//5.建立回撥佇列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
FrameHandler
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
//渲染下一個frame
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//請求VSNYC訊號
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//執行Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
FrameDisplayEventReceiver
FrameDisplayEventReceiver是DisplayEventReceiver的子類,DisplayEventReceiver是接收VSYNC資訊的java層實現。
public abstract class DisplayEventReceiver {
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {}
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
}
VSYNC資訊一般由硬體中斷產生,SurfaceFlinger處理。具體實現和監聽機制可以參考連結,scheduleVsync
方法用於請求VSNYC訊號, Native方法接收到VSYNC資訊處理後會呼叫java層dispatchVsync
方法,最終呼叫到FrameDisplayEventReceiver的onVsync
方法,具體實現我們一會再說。
CallbackQueue
CallbackQueue是個單鏈表實現,每種型別的callback(CallbackRecord)按照設定的執行時間(dueTime)順序排序分別儲存在其各自CallbackQueue。在Choreographer中有四種類型callback:Input、Animation、Draw,還有一種是用來解決動畫啟動問題的。
private final class CallbackQueue {
private CallbackRecord mHead;
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
//根據當前時間得到callback
public CallbackRecord extractDueCallbacksLocked(long now) {
....
....
}
//根據時間新增callback
public void addCallbackLocked(long dueTime, Object action, Object token) {
....
....
}
//移除callback
public void removeCallbacksLocked(Object action, Object token) {
....
....
}
}
}
2. 流程分析
大致分析完Choreographer關鍵的幾個成員變數後,我們再回到postFrameCallback
方法
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
//預設為CALLBACK_ANIMATION型別
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//新增callback到回撥佇列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//設定的執行時間在當前時間之後,傳送MSG_DO_SCHEDULE_CALLBACK,由FrameHanlder安排執行scheduleFrameLocked
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);`
}
}
}
scheduleFrameLocked
private void scheduleFrameLocked(long now) {
....
if (isRunningOnLooperThreadLocked()) {
//若當前執行緒是UI執行緒,執行scheduleVsyncLocked請求VSYNC訊號
scheduleVsyncLocked();
} else {
//非UI執行緒,傳送MSG_DO_SCHEDULE_VSYNC訊息到主執行緒
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
....
}
scheduleVsyncLocked最終呼叫FrameDisplayEventReceiver#scheduleVsync,收到Vsync資訊後,呼叫FrameDisplayEventReceiver#onVsync
FrameDisplayEventReceiver#onVsync
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper) {
super(looper);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
....
....
mTimestampNanos = timestampNanos;
mFrame = frame;
//該訊息的callback為當前物件FrameDisplayEventReceiver,收到訊息呼叫其run方法,然後呼叫doFrame方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
doFrame
void doFrame(long frameTimeNanos, int frame) {
....
//Vsync訊號到來時間
long intendedFrameTimeNanos = frameTimeNanos;
//實際開始執行當前frame的時間
startNanos = System.nanoTime();
//時間差
final long jitterNanos = startNanos - frameTimeNanos;
//時間差大於幀率,則認為是跳幀
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
....
....
//記錄當前frame資訊
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
//記錄上一次frame渲染的時間點
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//執行CallBack,優先順序為:CALLBACK_INPUT>CALLBACK_ANIMATION>CALLBACK_TRAVERSAL>CALLBACK_COMMIT
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
....
}
doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 從佇列查詢相應型別的CallbackRecord物件
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
....
....
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
....
//呼叫CallbackRecord的run方法
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
//回收callbacks,加入mCallbackPool物件池
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
CallbackRecord#run
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
//呼叫自定義FrameCallback的doFrame方法
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
至此,關於Choreographer的整個呼叫流程及其原理已經分析完成。至於系統某些呼叫,如View的invalidate
,觸發ViewRootImpl#scheduleTraversals
,最終呼叫
Choreographer#postCallback(Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);
,只是明確了Callbac的型別以及回撥處理Runnable而已,基本流程和自定義FrameCallback一樣。
總結
儘量避免在執行動畫或渲染操作之後在主執行緒執行操作,在之前或之後都應該儘量避免傳送訊息到主執行緒looper
既然自定義FrameCallback可以在下一個frame被渲染的時候會被回撥,那我們是不是可以根據這個原理實現應用的幀率監聽呢,答案是肯定的,下面是我的簡單實現:
1.自定義FrameCallback:FPSFrameCallback
public class FPSFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = "FPS_TEST";
private long mLastFrameTimeNanos = 0;
private long mFrameIntervalNanos;
public FPSFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
mFrameIntervalNanos = (long)(1000000000 / 60.0);
}
@Override
public void doFrame(long frameTimeNanos) {
//初始化時間
if (mLastFrameTimeNanos == 0) {
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if(skippedFrames>30){
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
mLastFrameTimeNanos=frameTimeNanos;
//註冊下一幀回撥
Choreographer.getInstance().postFrameCallback(this);
}
}
2.在Application中註冊
@Override
public void onCreate() {
super.onCreate();
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
}
3.測試
public class MainActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
LOG輸出如下:
I/Choreographer: Skipped 64 frames! The application may be doing too much work on its main thread.
I/FPS_TEST: Skipped 65 frames! The application may be doing too much work on its main thread.
基本和系統監控數值一致