前言
在Android4.1之后增加了Choreographer機制,用于同Vsync機制配合,控制同步處理輸入(Input)、影片(Animation)、繪制(Draw)三個UI操作,其實UI顯示的時候每一幀要完成的事情只有這三種,如下圖是官網的相關說明:
Choreographer接收顯示系統的時間脈沖(垂直同步信號-VSync信號),在下一個frame渲染時控制執行這些操作,
Choreographer中文翻譯過來是"舞蹈指揮",字面上的意思就是優雅地指揮以上三個UI操作一起跳一支舞,這個詞可以概括這個類的作業,如果android系統是一場芭蕾舞,他就是Android UI顯示這出精彩舞劇的編舞,指揮臺上的演員們相互合作,精彩演出,
通過Choreographer#postFrameCallback設定callback,在下一個frame被渲染時,會執行這個callback,是在Choreographer 中的Looper 執行的,例如:View的繪制流程,就是通過postFrameCallback設定回呼,然后被執行的,
Callback有4種型別,Input、Animation、Draw,還有一種是用來解決影片啟動問題的,這四種操作都是這么觸發的,

收到VSync信號后,順序執行3個操作,然后等待下一個信號,再次順序執行3個操作,假設在第二個信號到來之前,所有的操作都執行完成了,即Draw操作完成了,界面顯示第一幀的內容,那么第二個信號來到時,開始更新界面,每幀的時間不應超過 16ms,以達到每秒 60 幀的呈現速度(為什么是 60fps?)視覺上就不會感覺卡頓,
如果您的應用存在界面呈現緩慢的問題,系統會不得不跳過一些幀,這會導致用戶感覺您的應用不流暢,我們將這種情況稱為卡頓,
關于 幀 的概念,就像是影片的每一格影像,每一格就像是一張圖片,快速連續的播放,因為視覺的停留,看上去就是動態的, 而在Android系統中,這里的幀 是 指的是兩個VSync信號之間的這段時間,而不是具體待顯示的內容,跳幀的意思就是 例如 UI 操作A需要在16ms 內完成,但是它運行了35ms,那么下一個UI操作B,只能在35ms 后執行,本來UI操作B 要在16ms 開始執行的,我們認為它跳了一幀,(注意 UI操作B 執行的時候,Choreographer 會對齊VSync時鐘,也就是說認為他是在32ms 開始執行的)

第二個信號到來時,Draw操作沒有按時完成,導致第三個時鐘周期內顯示的還是第一幀的內容,
一、呼叫mChoreographer.postCallback
大家應該都熟悉界面的繪制流程了,下面看下ViewRootImpl的requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//檢查是否在主執行緒
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局,
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {//同一幀內不會多次呼叫遍歷
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//攔截同步Message
//Choreographer回呼,執行繪制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
二、Choreographer啟動
public ViewRootImpl(Context context, Display display) {
...
//獲取Choreographer實體
mChoreographer = Choreographer.getInstance();
...
}
public static Choreographer getInstance() {
return sThreadInstance.get();
}
//一個執行緒對應一個Choreographer
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper);
}
};
三、建構式
private Choreographer(Looper looper, int vsyncSource) {
//一個執行緒對應一個Looper,一個Choreographer
mLooper = looper;
//初始化FrameHandler,接收處理訊息,滿足條件的VSync信號的具體處理
mHandler = new FrameHandler(looper);
//初始化FrameDisplayEventReceiver,FrameDisplayEventReceiver用來接收垂直同步脈沖,就是VSync信號,VSync信號是一個時間脈沖,一般為60HZ,
//變數USE_VSYNC 表示是否用了Vsync同步機制
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
//每一幀的時間
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//回呼佇列,例如 postFrameCallback 傳入的callback,在處理下一陣的時候,回呼它
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
CallbackQueue 佇列的四種型別
public static final int CALLBACK_INPUT = 0; //輸入
public static final int CALLBACK_ANIMATION = 1; //影片
public static final int CALLBACK_TRAVERSAL = 2; //視圖繪制
public static final int CALLBACK_COMMIT = 3; //提交 ( 這一型別是在API level=23的時候添加的)
CallbackQueue是一個容量為4的陣列,每一個元素作為頭指標,引出對應型別的鏈表,4種事件就是通過這4個鏈表來維護的,其中每種型別都是使用單向鏈表來組織,和Message 中的訊息組織方式、創建、回收都是一樣的

四、流程分析
先來大概看一下,整體流程,有個整體輪廓,具體分析每一個函式時,不會迷路
圖片都是從網上找的,最后會給出原文鏈接

4.1、 FrameHandler
先來看看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:
//收到同步信號后的處理函式,也就是幀處理函式
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//請求垂直同步信號
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//處理 Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
4.2、postCallbackDelayedInternal
postCallback 和 postCallbackDelayed 最終都會呼叫到下面的postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
//獲取當前時間
final long now = SystemClock.uptimeMillis();
//計算出截止時間
final long dueTime = now + delayMillis;
//action 就是回呼介面,按照截止時間加入指定型別的佇列中,佇列截止時間順序是由小到大
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//當前時間已經超過截止時間了,直接執行
scheduleFrameLocked(now);
} else {
//當前時間未超過截止時間,發送msg,最終在FrameHandler 進行處理
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
4.3、scheduleFrameLocked
加鎖執行Frame,這個鎖是在呼叫scheduleFrameLocked的時候,外部添加的synchronized
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
//通過handler 發送MSG_DO_SCHEDULE_VSYNC訊息,最終也是呼叫scheduleVsyncLocked來處理的
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
//通過handler 發送MSG_DO_FRAME訊息,最終呼叫doFrame來處理
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
//請求垂直同步信號
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
4.4、DisplayEventReceiver#dispatchVsync
系統是通過呼叫dispatchVsync,發送VSync信號的,接著呼叫 onVsync來處理,Choreographer里的有個類FrameDisplayEventReceiver,它繼承了DisplayEventReceiver,并覆寫了onVsync
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
4.5、FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// Ignore vsync from secondary display.
// This can be problematic because the call to scheduleVsync() is a one-shot.
// We need to ensure that we will still receive the vsync from the primary
// display which is the one we really care about. Ideally we should schedule
// vsync for a particular display.
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
+ "of its scheduled vsync.");
scheduleVsync();
return;
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
//注釋中說,如果佇列中沒有時間戳早于幀時間的訊息,就
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
//這里callback 傳入this,就是下面的run 函式
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);
}
}
4.6、doFrame
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//開始處理幀任務前(在scheduleFrameLocked中),設為true,doFrame 處理完后,設為false,表示一幀處理完后,才會接受下一幀的處理
if (!mFrameScheduled) {
return; // no work to do
}
//發送幀的時間
long intendedFrameTimeNanos = frameTimeNanos;
//獲取當前時間
startNanos = System.nanoTime();
//計算出時間差,例如幀很早就發送了,但是過一會才得到處理
final long jitterNanos = startNanos - frameTimeNanos;
//時間差 大于幀的周期
if (jitterNanos >= mFrameIntervalNanos) {
//計算跳過了幾幀
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//下面這段log ,應該很熟悉了
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
//計算最后一幀的偏移
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
//幀對齊,與最后一幀的開始對齊
frameTimeNanos = startNanos - lastFrameOffset;
}
//為什么會出現時間回溯,我還沒搞清楚
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
//請求下一次VSync信號
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
//false 表示當前沒有正在處理的幀
mFrameScheduled = false;
//記錄上一次frame開始時間,修正后的
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//在doCallbacks 函式中,執行postCallbackDelayedInternal傳入的回呼介面
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
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 {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
關于上面幀對齊,下面來看兩種情況:
- 沒有跳幀
當前frame啟動時間直接設定為當前VSync信號時間,
雖然Frame 1執行時間超過一個VSync周期,但是因為沒有跳幀(沒有大于兩個周期),frame 2的執行時間還是設定為frameTimeNanos3

- 發生跳幀
修正當前frame的啟動時間到最近的VSync信號時間,
frame 1 執行時間超過2個時鐘周期,frame 2 延后執行時間大于一個時鐘周期,系統認為這時候影響較大,判定為跳幀了,將frame 2的時間修正為frameTimeNanos4,比VSync真正到來的時間晚了一個時鐘周期,

無論當前frame是否跳幀,修正完時間后,frame的處理時間與VSync信號的發送時間還是在一個節奏上的,可能延后了N個周期
4.7、doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
//extractDueCallbacksLocked 函式就是按照 當前的時間,把佇列劃分為兩部分,
//因為佇列是按照到期時間由小到大 排列的,以當前時間為界,分為兩部分,回傳小的部分佇列的頭,大的部分佇列的頭賦值給mHead
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
//提交幀時,如有必要,更新幀時間,
//僅在到達提交階段晚于2幀時才更新幀時間,
//這確保了回呼所觀察到的幀時間將始終從一幀增加到下一幀,并且永遠不會重復,
//我們永遠都不希望下一幀的開始幀時間小于或等于前一幀的提交幀時間,
//請記住,下一幀很可能已經安排好了,因此我們可以確保提交時間始終至少落后一幀,以確保安全
//CALLBACK_COMMIT 是為了解決,影片繪制時間過長導致的影片跳幀的問題
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
//時間差大于2倍的VSync周期,才去修正幀時間,
//是因為下面會把幀時間,往前移動一個完整周期,避免出現兩個幀的
if (jitterNanos >= 2 * mFrameIntervalNanos) {
//周期偏差時間 + 一個周期的時間
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
//開始回呼介面
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

上圖表達的不是很清楚,不用理會”修正commit time“虛線,frameTimeNanos2 修正到第三個VSync(修正后的commit time 實線指向的時間)
在 frame 1 開始執行時,開始渲染影片的第一個畫面,frame 1 執行時間超過了兩個時鐘周期,Draw操作執行結束后,這時候完成了影片第一幀的渲染,影片實際上還沒開始,但是時間已經過了兩個時鐘周期,此時還沒有開始影片的執行,影片是使用插值器,根據時間的流逝來計算比例,因此開始時間就顯得很關鍵,如果在第一幀渲染就使用了過多的時間,那么就需要調整影片的開始時間,這樣影片才能在第一幀開始播放
也就是說影片的開始時間需要往后移,移動多少呢?幀處理結束的時間 - (周期的偏移時間 + 一個周期時間)
為什么要移動這么多時間呢? 為了避免兩個frame 的幀時間相同,還記得前面doFrame函式嗎,它會把frame操作的到來時間 對齊到 VSync的頻率上,而且是向前對齊,也就是說如果frame 1 只是移動到幀處理結束的時間 - (周期的偏移時間),那么就會和frame 2 的幀時間相同(以上圖來說,就是都是frameTimeNanos3)
看了這么多,感覺終于理解了,但是在Android 8.0.0 原始碼中ValueAnimator 影片的開始時間調整,已經不使用這種方式了,也就是說CALLBACK_COMMIT沒有被使用了, 那又是如何解決這個問題的呢?我會在下片文章分析這個問題
4.8、CallbackRecord
佇列中保存的就是該類物件,執行到該類的run函式
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
4.9、回到開頭的requestLayout()
回呼requestLayout 中傳入的介面
void scheduleTraversals(){
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,null);
}
final class TraversalRunnable implements Runnable{
@Override
public void run(){
//開始繪制流程,會執行layout,measure,draw
doTraversal();
}
}
最后附上一張時序圖,來回顧一下整個流程

參考:
Android Choreographer 原始碼分析
提取該文章的部分圖片
Android 基于 Choreographer 的渲染機制詳解
提取該文章的部分一張圖片,它的部分代碼理解有誤
Android圖形系統(十一)-Choreographer
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/7917.html
標籤:其他
