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ì)更新单匣,還是現(xiàn)實(shí)上一個(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市胀溺,隨后出現(xiàn)的幾起案子仓坞,更是在濱河造成了極大的恐慌,老刑警劉巖嫉称,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件始藕,死亡現(xiàn)場(chǎng)離奇詭異伍派,居然都是意外死亡祥国,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)语御,“玉大人,你說(shuō)我怎么就攤上這事碉纺∫呤辏” “怎么了捧搞?”我有些...
    開(kāi)封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵殖氏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我宝鼓,道長(zhǎng)胡陪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任锻全,我火速辦了婚禮妈踊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呐伞。我一直安慰自己癣防,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布蓝丙。 她就那樣靜靜地躺著级遭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渺尘。 梳的紋絲不亂的頭發(fā)上装畅,一...
    開(kāi)封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音沧烈,去河邊找鬼掠兄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锌雀,可吹牛的內(nèi)容都是我干的蚂夕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腋逆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼婿牍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起惩歉,我...
    開(kāi)封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤等脂,失蹤者是張志新(化名)和其女友劉穎俏蛮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體上遥,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搏屑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粉楚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辣恋。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖模软,靈堂內(nèi)的尸體忽然破棺而出伟骨,到底是詐尸還是另有隱情,我是刑警寧澤燃异,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布携狭,位于F島的核電站,受9級(jí)特大地震影響回俐,放射性物質(zhì)發(fā)生泄漏逛腿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一鲫剿、第九天 我趴在偏房一處隱蔽的房頂上張望鳄逾。 院中可真熱鬧稻轨,春花似錦灵莲、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至线欲,卻和暖如春明场,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背李丰。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工苦锨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趴泌。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓舟舒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嗜憔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秃励,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容