Android Choreographer 源碼分析

Android系統(tǒng)從4.1(API 16)開(kāi)始加入Choreographer這個(gè)類來(lái)控制同步處理輸入(Input)、動(dòng)畫(Animation)、繪制(Draw)三個(gè)UI操作。其實(shí)UI顯示的時(shí)候每一幀要完成的事情只有這三種。如下圖是官網(wǎng)的相關(guān)說(shuō)明:

Choreographer

Choreographer接收顯示系統(tǒng)的時(shí)間脈沖(垂直同步信號(hào)-VSync信號(hào)),在下一個(gè)frame渲染時(shí)控制執(zhí)行這些操作。
Choreographer中文翻譯過(guò)來(lái)是"舞蹈指揮",字面上的意思就是優(yōu)雅地指揮以上三個(gè)UI操作一起跳一支舞。這個(gè)詞可以概括這個(gè)類的工作,如果android系統(tǒng)是一場(chǎng)芭蕾舞,他就是Android UI顯示這出精彩舞劇的編舞,指揮臺(tái)上的演員們相互合作,精彩演出。Google的工程師看來(lái)挺喜歡舞蹈的!
好了廢話不多說(shuō),下面讓我們來(lái)看看劇本是怎么設(shè)計(jì)的,Let's Read the fucking source code!
Choreographer的源碼位于android.view這個(gè)pakage中,是view層框架的一部分,Android studio里面搜一下就可以看到源碼了。
首先看看頭部的一些說(shuō)明,大體了解一下這個(gè)類是干嘛的,有助于我們理解接下來(lái)的源碼。 和官網(wǎng)的文檔是一樣的,應(yīng)該就是用這個(gè)生成的,和上面一部分相比介紹了Choreographer的使用接口。開(kāi)發(fā)者可以使用Choreographer#postFrameCallback設(shè)置自己的callback與Choreographer交互,你設(shè)置的callCack會(huì)在下一個(gè)frame被渲染時(shí)觸發(fā)。Callback有4種類型,Input、Animation、Draw,還有一種是用來(lái)解決動(dòng)畫啟動(dòng)問(wèn)題的,將在下文介紹。這四種操作都是這么觸發(fā)的。
如下圖:
Choreographer工作流程

收到VSync信號(hào)后,順序執(zhí)行3個(gè)操作,然后等待下一個(gè)信號(hào),再次順序執(zhí)行3個(gè)操作。假設(shè)在第二個(gè)信號(hào)到來(lái)之前,所有的操作都執(zhí)行完成了,即Draw操作完成了,那么第二個(gè)信號(hào)來(lái)到時(shí),此時(shí)界面將會(huì)更新為第一frame的內(nèi)容,因?yàn)镈raw操作已經(jīng)完成了。否則界面將不會(huì)更新,還是顯示上一個(gè)frame的內(nèi)容,表示你丟幀了。丟幀是造成卡頓的原因。如下圖:
丟幀

第二個(gè)信號(hào)到來(lái)時(shí),Draw操作沒(méi)有按時(shí)完成,導(dǎo)致第三個(gè)時(shí)鐘周期內(nèi)顯示的還是第一幀的內(nèi)容。
注意文檔的最后一段話:
Each Looper thread has its own choreographer. Other threads can post callbacks to run on the choreographer but they will run on the Looper to which the choreographer belongs.*
每個(gè)線程都有自己的choreographer。

基本上的原理就是上面這樣,那么接下來(lái)我們通過(guò)源碼詳細(xì)地看一下細(xì)節(jié)是怎么實(shí)現(xiàn)的。
首先先看看構(gòu)造函數(shù)。

構(gòu)造函數(shù)

private Choreographer(Looper looper) {    
  mLooper = looper;    
  mHandler = new FrameHandler(looper);    
  mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : 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();    
  }
}

這里做了幾個(gè)初始化操作,根據(jù)Looper對(duì)象生成,Looper和線程是一對(duì)一的關(guān)系,對(duì)應(yīng)上面說(shuō)明里的每個(gè)線程對(duì)應(yīng)一個(gè)Choreographer。

1.初始化FrameHandler。接收處理消息。

2.初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用來(lái)接收垂直同步脈沖,就是VSync信號(hào),VSync信號(hào)是一個(gè)時(shí)間脈沖,一般為60HZ,用來(lái)控制系統(tǒng)同步操作,怎么同ChoreoGrapher一起工作的,將在下文介紹。

3.初始化mLastFrameTimeNanos(標(biāo)記上一個(gè)frame的渲染時(shí)間)以及mFrameIntervalNanos(幀率,fps,一般手機(jī)上為1s/60)。

4.初始化CallbackQueue,callback隊(duì)列,將在下一幀開(kāi)始渲染時(shí)回調(diào)。

我們首先看看FrameHandler和FrameDisplayEventReceiver的結(jié)構(gòu)。

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:                
        doScheduleCallback(msg.arg1);                
      break;        
  }    
  }
}

看上面的代碼,就是一個(gè)簡(jiǎn)單的Handler。處理3個(gè)類型的消息。

MSG_DO_FRAME:開(kāi)始渲染下一幀的操作

MSG_DO_SCHEDULE_VSYNC:請(qǐng)求Vsync信號(hào)

MSG_DO_SCHEDULE_CALLBACK:請(qǐng)求執(zhí)行callback

額,下面再細(xì)分一下,分別詳細(xì)看一下這三個(gè)步驟是怎么實(shí)現(xiàn)的。繼續(xù)看源碼吧。。。

FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver        
implements Runnable {    
  public FrameDisplayEventReceiver(Looper looper) {    
    super(looper);
  }
  @Override 
  public void onVsync(long timestampNanos, int  builtInDisplayId, 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);    
  }
}

FrameDisplayEventReceiver繼承自DisplayEventReceiver接收底層的VSync信號(hào)開(kāi)始處理UI過(guò)程。VSync信號(hào)由SurfaceFlinger實(shí)現(xiàn)并定時(shí)發(fā)送。FrameDisplayEventReceiver收到信號(hào)后,調(diào)用onVsync方法組織消息發(fā)送到主線程處理。這個(gè)消息主要內(nèi)容就是run方法里面的doFrame了,這里mTimestampNanos是信號(hào)到來(lái)的時(shí)間參數(shù)。

FrameHandler和FrameDisplayEventReceiver是怎么工作的呢?ChoreoGrapher的總體流程圖如下圖:

流程圖

choreographer流程圖

以上是總體的流程圖:
1.PostCallBack,發(fā)起添加回調(diào),這個(gè)FrameCallBack將在下一幀被渲染時(shí)執(zhí)行。

2.AddToCallBackQueue,將FrameCallBack添加到回調(diào)隊(duì)列里面,等待時(shí)機(jī)執(zhí)行回調(diào)。每種類型的callback按照設(shè)置的執(zhí)行時(shí)間(dueTime)順序排序分別保存在一個(gè)單鏈表中。

3.判斷FrameCallBack設(shè)定的執(zhí)行時(shí)間是否在當(dāng)前時(shí)間之后,若是,發(fā)送MSG_DO_SCHEDULE_CALLBACK消息到主線程,安排執(zhí)行doScheduleCallback,安排執(zhí)行CallBack。否則直接跳到第4步。

4.執(zhí)行scheduleFrameLocked,安排執(zhí)行下一幀。

5.判斷上一幀是否已經(jīng)執(zhí)行,若未執(zhí)行,當(dāng)前操作直接結(jié)束。若已經(jīng)執(zhí)行,根據(jù)情況執(zhí)行以下6、7步。

6.若使用垂直同步信號(hào)進(jìn)行同步,則執(zhí)行7.否則,直接跳到9。

7.若當(dāng)前線程是UI線程,則通過(guò)執(zhí)行scheduleVsyncLocked請(qǐng)求垂直同步信號(hào)。否則,送MSG_DO_SCHEDULE_VSYNC消息到主線程,安排執(zhí)行doScheduleVsync,在主線程調(diào)用scheduleVsyncLocked。

8.收到垂直同步信號(hào),調(diào)用FrameDisplayEventReceiver.onVsync(),發(fā)送消息到主線程,請(qǐng)求執(zhí)行doFrame。

9.執(zhí)行doFrame,渲染下一幀。

主要的工作在doFrame中,接下來(lái)我們具體看看doFrame函數(shù)都干了些什么。
從名字看很容易理解doFrame函數(shù)就是開(kāi)始進(jìn)行下一幀的顯示工作。好了以下源代碼又來(lái)了,我們一行一行分析一下吧。

doFrame

void doFrame(long frameTimeNanos, int frame) {    
  final long startNanos;    
  synchronized (mLock) {        
    if (!mFrameScheduled) { //判斷是否有callback需要執(zhí)行,mFrameScheduled會(huì)在postCallBack的時(shí)候置為true,一次frame執(zhí)行時(shí)置為false       
      return; // no work to do        
    }
    \\\\打印跳frame時(shí)間        
    if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {            
      mDebugPrintNextFrameTimeDelta = false;            
      Log.d(TAG, "Frame time delta: "                    
              + ((frameTimeNanos - mLastFrameTimeNanos) *  0.000001f) + " ms");        
    }
    //設(shè)置當(dāng)前frame的Vsync信號(hào)到來(lái)時(shí)間        
    long intendedFrameTimeNanos = frameTimeNanos;        
    startNanos = System.nanoTime();//實(shí)際開(kāi)始執(zhí)行當(dāng)前frame的時(shí)間
    //時(shí)間差        
    final long jitterNanos = startNanos - frameTimeNanos;        
    if (jitterNanos >= mFrameIntervalNanos) {
      //時(shí)間差大于一個(gè)時(shí)鐘周期,認(rèn)為跳frame            
      final long skippedFrames = jitterNanos / mFrameIntervalNanos;
      //跳frame數(shù)大于默認(rèn)值,打印警告信息,默認(rèn)值為30            
      if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {                
         Log.i(TAG, "Skipped " + skippedFrames + " frames!  "                        
                    + "The application may be doing too much work on its main thread.");            
      }
      //計(jì)算實(shí)際開(kāi)始當(dāng)前frame與時(shí)鐘信號(hào)的偏差值            
      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.");            
       }
       //修正偏差值,忽略偏差,為了后續(xù)更好地同步工作            
       frameTimeNanos = startNanos - lastFrameOffset;        
    }
    //若時(shí)間回溯,則不進(jìn)行任何工作,等待下一個(gè)時(shí)鐘信號(hào)的到來(lái)
    //這里為什么會(huì)發(fā)生時(shí)間回溯我沒(méi)搞明白,大概是未知時(shí)鐘錯(cuò)誤引起?注釋里說(shuō)的maybe 好像不太對(duì)        
    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.");            
   }
   //請(qǐng)求下一次時(shí)鐘信號(hào)            
   scheduleVsyncLocked();            
   return;        
  }
 //記錄當(dāng)前frame信息        
 mFrameInfo.setVsync(intendedFrameTimeNanos,frameTimeNanos);        
 mFrameScheduled = false;
 //記錄上一次frame開(kāi)始時(shí)間,修正后的        
 mLastFrameTimeNanos = frameTimeNanos;    
 }    
  try {
    //執(zhí)行相關(guān)callBack        
    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);    
  }    
  if (DEBUG_FRAMES) {        
    final long endNanos = System.nanoTime();        
    Log.d(TAG, "Frame " + frame + ": Finished, took "                
              + (endNanos - startNanos) * 0.000001f + " ms, latency "                
              + (startNanos - frameTimeNanos) * 0.000001f + " ms.");    
   }
}

大部分內(nèi)容都在上面的注釋中說(shuō)明了,大概是以下的流程:


doFrame流程圖

總結(jié)起來(lái)其實(shí)主要是兩個(gè)操作:

1.設(shè)置當(dāng)前frame的啟動(dòng)時(shí)間。
判斷是否跳幀,若跳幀修正當(dāng)前frame的啟動(dòng)時(shí)間到最近的VSync信號(hào)時(shí)間。如果沒(méi)跳幀,當(dāng)前frame啟動(dòng)時(shí)間直接設(shè)置為當(dāng)前VSync信號(hào)時(shí)間。修正完時(shí)間后,無(wú)論當(dāng)前frame是否跳幀,使得當(dāng)前frame的啟動(dòng)時(shí)間與VSync信號(hào)還是在一個(gè)節(jié)奏上的,可能可能延后了一到幾個(gè)周期,但是節(jié)奏點(diǎn)還是吻合的。
如下圖所示是時(shí)間修正的一個(gè)例子,


沒(méi)有跳幀但延遲

由于第二個(gè)frame執(zhí)行超時(shí),第三個(gè)frame實(shí)際啟動(dòng)時(shí)間比第三個(gè)VSync信號(hào)到來(lái)時(shí)間要晚,因?yàn)檫@時(shí)候延時(shí)比較小,沒(méi)有超過(guò)一個(gè)時(shí)鐘周期,系統(tǒng)還是將frameTimeNanos3傳給回調(diào),回調(diào)拿到的時(shí)間和VSync信號(hào)同步。
再來(lái)看看下圖:


跳幀

由于第二個(gè)frame執(zhí)行時(shí)間超過(guò)2個(gè)時(shí)鐘周期,導(dǎo)致第三個(gè)frame延后執(zhí)行時(shí)間大于一個(gè)時(shí)鐘周期,系統(tǒng)認(rèn)為這時(shí)候影響較大,判定為跳幀了,將第三個(gè)frame的時(shí)間修正為frameTimeNanos4,比VSync真正到來(lái)的時(shí)間晚了一個(gè)時(shí)鐘周期。
時(shí)間修正,既保證了doFrame操作和VSync保持同步節(jié)奏,又保證實(shí)際啟動(dòng)時(shí)間與記錄的時(shí)間點(diǎn)相差不會(huì)太大,便于同步及分析。

2.順序執(zhí)行callBack隊(duì)列里面的callback.

然后接下來(lái)看看doCallbacks的執(zhí)行過(guò)程:

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();        
        callbacks =  mCallbackQueues[callbackType].extractDueCallbacksLocked(                now / TimeUtils.NANOS_PER_MS);        
        if (callbacks == null) {            
              return;        
        }        
        mCallbacksRunning = true;        
        // Update the frame time if necessary when committing the frame.
        // We only update the frame time if we are more than 2 frames late reaching
        // the commit phase.  This ensures that the frame time which is observed by the
        // callbacks will always increase from one frame to the next and never repeat.
        // We never want the next frame's starting frame time to end up being less than
        // or equal to the previous frame's commit frame time.  Keep in mind that the
        // next frame has most likely already been scheduled by now so we play it
        // safe by ensuring the commit time is always at least one frame behind.
        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;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
                            + " ms which is more than twice the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Setting frame time to " +(lastFrameOffset * 0.000001f)
                            + " ms in the past.");
                    mDebugPrintNextFrameTimeDelta = true;
                }
                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) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "RunCallback: type=" + callbackType
                        + ", action=" + c.action + ", token=" + c.token
                        + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
            }
            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);
    }
}

callback的類型有以下4種,除了文章一開(kāi)始提到的3中外,還有一個(gè)CALLBACK_COMMIT。

CALLBACK_INPUT:輸入
CALLBACK_ANIMATION:動(dòng)畫
CALLBACK_TRAVERSAL:遍歷,執(zhí)行measure、layout、draw
CALLBACK_COMMIT:遍歷完成的提交操作,用來(lái)修正動(dòng)畫啟動(dòng)時(shí)間

然后看上面的源碼,分析一下每個(gè)callback的執(zhí)行過(guò)程:

1.callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS);得到執(zhí)行時(shí)間在當(dāng)前時(shí)間之前的所有CallBack,保存在單鏈表中。每種類型的callback按執(zhí)行時(shí)間先后順序排序分別存在一個(gè)單鏈表里面。為了保證當(dāng)前callback執(zhí)行時(shí)新post進(jìn)來(lái)的callback在下一個(gè)frame時(shí)才被執(zhí)行,這個(gè)地方extractDueCallbacksLocked會(huì)將需要執(zhí)行的callback和以后執(zhí)行的callback斷開(kāi)變成兩個(gè)鏈表,新post進(jìn)來(lái)的callback會(huì)被放到后面一個(gè)鏈表中。當(dāng)前frame只會(huì)執(zhí)行前一個(gè)鏈表中的callback,保證了在執(zhí)行callback時(shí),如果callback中Post相同類型的callback,這些新加的callback將在下一個(gè)frame啟動(dòng)后才會(huì)被執(zhí)行。

2.接下來(lái),看一大段注釋,如果類型是CALLBACK_COMMIT,并且當(dāng)前frame渲染時(shí)間超過(guò)了兩個(gè)時(shí)鐘周期,則將當(dāng)前提交時(shí)間修正為上一個(gè)垂直同步信號(hào)時(shí)間。為了保證下一個(gè)frame的提交時(shí)間和當(dāng)前frame時(shí)間相差為一且不重復(fù)。
這個(gè)地方注釋挺難看懂,實(shí)際上這個(gè)地方CALLBACK_COMMIT是為了解決ValueAnimator的一個(gè)問(wèn)題而引入的,主要是解決因?yàn)楸闅v時(shí)間過(guò)長(zhǎng)導(dǎo)致動(dòng)畫時(shí)間啟動(dòng)過(guò)長(zhǎng),時(shí)間縮短,導(dǎo)致跳幀,這里修正動(dòng)畫第一個(gè)frame開(kāi)始時(shí)間延后來(lái)改善,這時(shí)候才表示動(dòng)畫真正啟動(dòng)。為什么不直接設(shè)置當(dāng)前時(shí)間而是回溯一個(gè)時(shí)鐘周期之前的時(shí)間呢?看注釋,這里如果設(shè)置為當(dāng)前frame時(shí)間,因?yàn)閯?dòng)畫的第一個(gè)frame其實(shí)已經(jīng)繪制完成,第二個(gè)frame這時(shí)候已經(jīng)開(kāi)始了,設(shè)置為當(dāng)前時(shí)間會(huì)導(dǎo)致這兩個(gè)frame時(shí)間一樣,導(dǎo)致沖突。詳細(xì)情況請(qǐng)看官方針對(duì)這個(gè)問(wèn)題的修改。Fix animation start jank due to expensive layout operations.

如下圖所示:


修正commit時(shí)間

比如說(shuō)在第二個(gè)frame開(kāi)始執(zhí)行時(shí),開(kāi)始渲染動(dòng)畫的第一個(gè)畫面,第二個(gè)frame執(zhí)行時(shí)間超過(guò)了兩個(gè)時(shí)鐘周期,Draw操作執(zhí)行結(jié)束后,這時(shí)候完成了動(dòng)畫第一幀的渲染,動(dòng)畫實(shí)際上還沒(méi)開(kāi)始,但是時(shí)間已經(jīng)過(guò)了兩個(gè)時(shí)鐘周期,后面動(dòng)畫實(shí)際執(zhí)行時(shí)間將會(huì)縮短一個(gè)時(shí)鐘周期。這時(shí)候系統(tǒng)通過(guò)修正commit時(shí)間到frameTimeNanos的上一個(gè)VSync信號(hào)時(shí)間,即完成動(dòng)畫第一幀渲染之前的VSync信號(hào)到來(lái)時(shí)間,修正了動(dòng)畫啟動(dòng)時(shí)間,保證動(dòng)畫執(zhí)行時(shí)間的正確性。

3.接下來(lái)就是調(diào)用c.run(frameTimeNanos);執(zhí)行回調(diào)。
例如,你可以寫一個(gè)自定義的FPSFrameCallback繼承自Choreographer.FrameCallback,實(shí)現(xiàn)里面的doFrame方法。

public class FPSFrameCallback implements Choreographer.FrameCallback{
@Override
  public void doFrame(long frameTimeNanos){
      //do something
  }
}

通過(guò)
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback());
把你的回調(diào)添加到Choreographer之中,那么在下一個(gè)frame被渲染的時(shí)候就會(huì)回調(diào)你的callback,執(zhí)行你定義的doFrame操作,這時(shí)候你就可以獲取到這一幀的開(kāi)始渲染時(shí)間并做一些自己想做的事情了。
開(kāi)源組件Tiny Dancer就是根據(jù)這個(gè)原理獲取每一幀的渲染時(shí)間,繼而分析實(shí)現(xiàn)獲取設(shè)備的當(dāng)前幀率的。有興趣的人可以查看。
Tiny Dancer

好了,關(guān)于Choreographer的分析到此結(jié)束。希望對(duì)你有幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容