爲了更好的理解使用
Choreographer
監控App FPS
的原理,本文先來梳理一下Choreographer
的工做原理。git
Choreographer
主要是用來協調動畫、輸入和繪製事件運行的。它經過接收Vsync
信號來調度應用下一幀渲染時的動做。github
Choreographer
會經過Choreographer.FrameDisplayEventReceiver
來監聽底層HWC
觸發的Vsync
信號:bash
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
}
複製代碼
Vsync
信號能夠理解爲底層硬件的一個系統中斷,它每16ms會產生一次。上面onVsync()
的每一個參數的意義爲:ide
timestampNanos : Vsync信號到來的時間, 這個時間使用的是底層
JVM nanoscends -> System.nanoTime
oop
builtInDisplayId : 此時
SurfaceFlinger
內置的display id
post
frame : 幀號,隨着
onVsync
的回調數增長動畫
那onVsync
何時會調用呢?ui
Choreographer.scheduleVsyncLocked()
會請求下一次Vsync
信號到來時回調FrameDisplayEventReceiver.onVsync()
方法:spa
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
複製代碼
Choregrapher
提供了下面方法設置callback
:線程
public void postCallback(int callbackType, Runnable action, Object token)
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis)
public void postFrameCallback(FrameCallback callback)
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)
複製代碼
在Choregrapher
中存在多個Callback Queue
, 常見的Callback Queue
的類型有:
Choreographer.CALLBACK_INPUT 輸入事件,好比鍵盤
Choreographer.CALLBACK_ANIMATION 動畫
Choreographer.CALLBACK_TRAVERSAL 好比`ViewRootImpl.scheduleTraversals, layout or draw`
Choreographer.CALLBACK_COMMIT
複製代碼
上面4個事件會在一次Vsync
信號到來時依次執行。
以ViewRootImpl.scheduleTraversals
爲例:
void scheduleTraversals() {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
複製代碼
即給Choregrapher
提交了一個Choreographer.CALLBACK_TRAVERSAL
類型的callback
去執行。
postCallback()
裏面的具體執行邏輯就不分析了,這裏直接說一下關鍵邏輯:
Choregrapher
建立時所在的線程去調用scheduleFrameLocked()
方法,設置mFrameScheduled = true
scheduleVsyncLocked
請求下一次Vsync
信號回調FrameDisplayEventReceiver.onVsync()
會生成一個消息,而後發送到Choreographer.mHander
的消息隊列Choreographer.mHander
取出上面onVsync
中發送的消息,執行Choreographer.doFrame()
方法,doFrame()
中判斷mFrameScheduled
是否爲true
,若是爲true
的話就上面四種callback
綜上所述Choreographer
的工做原理以下圖:
咱們來看一下這個方法(主要關注一下時間參數):
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
if (!mFrameScheduled) {
return; // no work to do
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) { //16ms
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
frameTimeNanos = startNanos - lastFrameOffset;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
複製代碼
解釋一下上面一些時間相關參數的含義:
intendedFrameTimeNanos: 預計這一幀開始渲染的時間
frameTimeNanos: 這一幀真正開始渲染的時間。在
startNanos - frameTimeNanos < mFrameIntervalNanos
,其實就等於intendedFrameTimeNanos
jitterNanos: 真正渲染時間點和預計渲染時間點之差
mFrameIntervalNanos: 每一幀指望渲染的時間, 固定爲16ms
skippedFrames : jitterNanos總共跳過了多少幀。
mLastFrameTimeNanos : 上一次渲染一幀的時間點
那 jitterNanos > mFrameIntervalNanos
在何時會成立呢?
其實就是咱們常說的丟幀: 好比咱們連續提交了兩個Choreographer.CALLBACK_TRAVERSAL callback
。若是一個callback
的執行時間大於16ms,那麼就會形成:
startNanos - frameTimeNanos > mFrameIntervalNanos(16ms)
複製代碼
這個方法的邏輯並不複雜 : 獲取callbackType
對應的Callback Queue
, 取出這個隊列中已通過期的calllback
進行執行。
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
}
複製代碼
上面咱們已經知道onVsync
把要執行doFrame
的消息放入了Choreographer.mHander
的消息隊列。
這裏Choreographer.mHander的消息隊列其實就是主線程的消息,因此doFrame方法實際上是由主線程的消息循環來調度的。
咱們看一下Choreographer
實例化時的Looper
:
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
...
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
複製代碼
即取的是當前線程的Looper
,因此donFrame()
是在主線程的消息循環中調度的。
參考文章:
Android Choreographer 源碼
後面會分析Tencent/matrix的實現原理,歡迎關注個人Android進階計劃看更多文章。