Choreographer全解析


 

 

前言

今天繼續屏幕刷新機制的知識講解,上文說到vsync的處理,每一幀UI的繪制前期處理都在Choreographer中實現,那么今天就來看看這個神奇的舞蹈編舞師是怎么將UI變化反應到屏幕上的。

代碼未動,圖先行

UI變化

上期說到app並不是每一個vsync信號都能接收到的,只有當應用有繪制需求的時候,才會通過scheduledVsync 方法申請VSYNC信號。

那我們就從有繪制需求開始看,當我們修改了UI后,都會執行invalidate方法進行繪制,這里我們舉例setText方法,再回顧下修改UI時候的流程:

可以看到,最后會調用到父布局ViewRootImplscheduleTraversals方法。

    public ViewRootImpl(Context context, Display display) { //... mChoreographer = Choreographer.getInstance(); } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //... } } 

為了方便查看,我只留了相關代碼。可以看到,在ViewRootImpl構造方法中,實例化了Choreographer對象,並且在發現UI變化的時候調用的scheduleTraversals方法中,調用了postSyncBarrier方法插入了同步屏障,然后調用了postCallback方法,並且傳入了一個mTraversalRunnable(后面有用處,先留意一下),暫時還不知道這個方法是干嘛的。繼續看看。

Choreographer實例化

//Choreographer.java public static Choreographer getInstance() { return sThreadInstance.get(); } 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); //... return choreographer; } }; private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; mHandler = new FrameHandler(looper); //初始化FrameDisplayEventReceiver mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; //一幀間隔時間 mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } } 

ThreadLocal,是不是有點熟悉?之前說Handler的時候說過,Handler是怎么獲取當前線程的Looper的?就是通過這個ThreadLocal,同樣,這里也是用到ThreadLocal來保證每個線程對應一個Choreographer

存儲方法還是一樣,以ThreadLocal為key,Choreographer為value存儲到ThreadLocalMap中,不熟悉的朋友可以再翻到《Handler另類難點三問》看看。

所以這里創建的mHandler就是ViewRootImpl所處的線程的handler。接着看postCallback做了什么。

postCallback

    private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { if (DEBUG_FRAMES) { Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token + ", delayMillis=" + delayMillis); } synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } } 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: doScheduleCallback(msg.arg1); break; } } } void doScheduleCallback(int callbackType) { synchronized (mLock) { if (!mFrameScheduled) { final long now = SystemClock.uptimeMillis(); if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) { scheduleFrameLocked(now); } } } } 

ViewRootImpl中調用了postCallback方法之后,可以看到通過addCallbackLocked方法,添加了一條CallbackRecord數據,其中action就是對應之前ViewRootImplmTraversalRunnable

然后判斷設定的時間是否在當前時間之后,也就是有沒有延遲,如果有延遲就發送延遲消息消息MSG_DO_SCHEDULE_CALLBACK到Handler所在線程,並最終執行到scheduleFrameLocked方法。如果沒有延遲,則直接執行scheduleFrameLocked

scheduleFrameLocked(准備申請VSYNC信號)

    private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { //是否運行在主線程 if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else { //通過Handler切換線程 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); Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } } case MSG_DO_FRAME: doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); break; void doScheduleVsync() { synchronized (mLock) { if (mFrameScheduled) { scheduleVsyncLocked(); } } } 

該方法中,首先判斷了是否開啟了VSYNC(上節說過Android4.1之后默認開啟VSYNC),如果開啟了,判斷在不在主線程,如果是主線程就運行scheduleVsyncLocked,如果不在就切換線程,也會調用到scheduleVsyncLocked方法,而這個方法就是我們之前說過的申請VSYNC信號的方法了。

如果沒有開啟VSYNC,則直接調用doFrame方法。

另外可以看到,剛才我們用到Handler發送消息的時候,都調用了msg.setAsynchronous(true)方法,這個方法就是設置消息為異步消息。因為我們剛才一開始的時候設置了同步屏障,所以異步消息就會先執行,這里的設置異步也就是為了讓消息第一時間執行而不受其他Handler消息影響。

小結1

通過上面一系列方法,我們能得到一個初步的邏輯過程了:

  • ViewRootImpl初始化的時候,會實例化Choreographer對象,也就是獲取當前線程(一般就是主線程)對應的Choreographer對象。
  • Choreographer初始化的時候,會新建一個當前線程對應的Handler對象,初始化FrameDisplayEventReceiver,計算一幀的時間等一系列初始化工作。
  • 當UI改變的時候,會調用到ViewRootImplscheduleTraversals方法,這個方法中加入了同步屏障消息,並且調用了Choreographer的postCallback方法去申請VSYNC信號。

在這個過程中,Handler發送了延遲消息,切換了線程,並且給消息都設置了異步,保證最先執行。

繼續看scheduleVsyncLocked方法。

scheduleVsyncLocked

    private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } 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); } } 

代碼很簡單,就是通過FrameDisplayEventReceiver,請求native層面的垂直同步信號VSYNC。

這個FrameDisplayEventReceiver是在Choreographer構造方法中實例化的,繼承自DisplayEventReceiver,主要就是處理VSYNC信號的申請和接收。

剛才說到調用nativeScheduleVsync方法申請VSYNC信號,然后當收到VSYNC信號的時候就會回調onVsync方法了。

onVsync(接收VSYNC信號)

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { //... mTimestampNanos = timestampNanos; mFrame = frame; 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); } } 

這里同樣通過Handler發送了一條消息,執行了本身的Runnable回調方法,也就是doFrame()

doFrame(繪制幀數據)

    void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { //... //當前幀的vsync信號來的時間,假如為12分200ms long intendedFrameTimeNanos = frameTimeNanos; //當前時間,也就是開始繪制的時間,假如為12分150ms startNanos = System.nanoTime(); //計算時間差,如果大於一個幀時間,則是跳幀了。比如是50ms,大於16ms final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { //計算掉了幾幀,50/16=3幀 final long skippedFrames = jitterNanos / mFrameIntervalNanos; //計算一幀內時間差,50%16=2ms final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; //修正時間,vsync信號應該來得時間,為12分148ms,保證和繪制時間對應上 frameTimeNanos = startNanos - lastFrameOffset; } if (frameTimeNanos < mLastFrameTimeNanos) { //信號時間已過,不能再繪制了,等待下一個vsync信號,保證后續時間同步上 scheduleVsyncLocked(); return; } mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try { //執行相關的callback任務 mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } 

這里主要的工作就是:

  • 設置當前幀的開始繪制時間,上節說過開始繪制要在vsync信號來的時候開始,保證兩者時間對應。所以如果時間沒對上,就是發送了跳幀,那么就要修正這個時間,保證后續的時間對應上。
  • 執行所有的Callback任務。

doCallbacks(執行任務)

    void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; if (callbackType == Choreographer.CALLBACK_COMMIT) { final long jitterNanos = now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); 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); } } private static final class CallbackRecord { public CallbackRecord next; public long dueTime; public Object action; // Runnable or FrameCallback public Object token; @UnsupportedAppUsage public void run(long frameTimeNanos) { if (token == FRAME_CALLBACK_TOKEN) { ((FrameCallback)action).doFrame(frameTimeNanos); } else { ((Runnable)action).run(); } } } 

其實就是按類型,從mCallbackQueues隊列中取任務,並執行對應的CallbackRecord的run方法。

而這個run方法中,判斷了token,並執行了action的對應方法。再回頭看看我們當初ViewRootImpl傳入的方法:

mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 

token為空,所以會執行到action也就是mTraversalRunnable的run方法。

所以兜兜轉轉,又回到了ViewRootImpl本身,通過Choreographer申請了VSYNC信號,然后接收了VSYNC信號,最終回到自己這里,開始view的繪制。

最后看看mTraversalRunnable的run方法。

mTraversalRunnable

    final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } } 

這就很熟悉了吧,調用了performTraversals方法,也就是開始了測量,布局,繪制的步驟。同時,關閉了同步屏障。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM