Android Choreographer 源碼分析

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

Choreographer

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

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

第二個(gè)信號(hào)到來時(shí)蜘拉,Draw操作沒有按時(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旭旭。

基本上的原理就是上面這樣,那么接下來我們通過源碼詳細(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)上面說明里的每個(gè)線程對(duì)應(yīng)一個(gè)Choreographer。

1.初始化FrameHandler荠卷。接收處理消息模庐。

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

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

4.初始化CallbackQueue,callback隊(duì)列湃交,將在下一幀開始渲染時(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è)簡單的Handler息罗。處理3個(gè)類型的消息。

MSG_DO_FRAME:開始渲染下一幀的操作

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)開始處理UI過程岁歉。VSync信號(hào)由SurfaceFlinger實(shí)現(xiàn)并定時(shí)發(fā)送得运。FrameDisplayEventReceiver收到信號(hào)后,調(diào)用onVsync方法組織消息發(fā)送到主線程處理锅移。這個(gè)消息主要內(nèi)容就是run方法里面的doFrame了熔掺,這里mTimestampNanos是信號(hào)到來的時(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線程薛匪,則通過執(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中苗胀,接下來我們具體看看doFrame函數(shù)都干了些什么。
從名字看很容易理解doFrame函數(shù)就是開始進(jìn)行下一幀的顯示工作瓦堵。好了以下源代碼又來了,我們一行一行分析一下吧歌亲。

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)到來時(shí)間        
    long intendedFrameTimeNanos = frameTimeNanos;        
    startNanos = System.nanoTime();//實(shí)際開始執(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í)際開始當(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)的到來
    //這里為什么會(huì)發(fā)生時(shí)間回溯我沒搞明白飞蚓,大概是未知時(shí)鐘錯(cuò)誤引起滤港?注釋里說的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開始時(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)容都在上面的注釋中說明了趴拧,大概是以下的流程:


doFrame流程圖

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

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


沒有跳幀但延遲

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


跳幀

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

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

然后接下來看看doCallbacks的執(zhí)行過程:

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種,除了文章一開始提到的3中外撞芍,還有一個(gè)CALLBACK_COMMIT秧了。

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

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

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)來的callback在下一個(gè)frame時(shí)才被執(zhí)行狮辽,這個(gè)地方extractDueCallbacksLocked會(huì)將需要執(zhí)行的callback和以后執(zhí)行的callback斷開變成兩個(gè)鏈表,新post進(jìn)來的callback會(huì)被放到后面一個(gè)鏈表中。當(dāng)前frame只會(huì)執(zhí)行前一個(gè)鏈表中的callback隘竭,保證了在執(zhí)行callback時(shí)塘秦,如果callback中Post相同類型的callback,這些新加的callback將在下一個(gè)frame啟動(dòng)后才會(huì)被執(zhí)行动看。

2.接下來尊剔,看一大段注釋,如果類型是CALLBACK_COMMIT菱皆,并且當(dāng)前frame渲染時(shí)間超過了兩個(gè)時(shí)鐘周期须误,則將當(dāng)前提交時(shí)間修正為上一個(gè)垂直同步信號(hào)時(shí)間。為了保證下一個(gè)frame的提交時(shí)間和當(dāng)前frame時(shí)間相差為一且不重復(fù)仇轻。
這個(gè)地方注釋挺難看懂京痢,實(shí)際上這個(gè)地方CALLBACK_COMMIT是為了解決ValueAnimator的一個(gè)問題而引入的,主要是解決因?yàn)楸闅v時(shí)間過長導(dǎo)致動(dòng)畫時(shí)間啟動(dòng)過長篷店,時(shí)間縮短祭椰,導(dǎo)致跳幀,這里修正動(dòng)畫第一個(gè)frame開始時(shí)間延后來改善疲陕,這時(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)開始了诅岩,設(shè)置為當(dāng)前時(shí)間會(huì)導(dǎo)致這兩個(gè)frame時(shí)間一樣讳苦,導(dǎo)致沖突。詳細(xì)情況請(qǐng)看官方針對(duì)這個(gè)問題的修改吩谦。Fix animation start jank due to expensive layout operations.

如下圖所示:


修正commit時(shí)間

比如說在第二個(gè)frame開始執(zhí)行時(shí)鸳谜,開始渲染動(dòng)畫的第一個(gè)畫面,第二個(gè)frame執(zhí)行時(shí)間超過了兩個(gè)時(shí)鐘周期逮京,Draw操作執(zhí)行結(jié)束后卿堂,這時(shí)候完成了動(dòng)畫第一幀的渲染,動(dòng)畫實(shí)際上還沒開始懒棉,但是時(shí)間已經(jīng)過了兩個(gè)時(shí)鐘周期,后面動(dòng)畫實(shí)際執(zhí)行時(shí)間將會(huì)縮短一個(gè)時(shí)鐘周期览绿。這時(shí)候系統(tǒng)通過修正commit時(shí)間到frameTimeNanos的上一個(gè)VSync信號(hào)時(shí)間策严,即完成動(dòng)畫第一幀渲染之前的VSync信號(hào)到來時(shí)間,修正了動(dòng)畫啟動(dòng)時(shí)間饿敲,保證動(dòng)畫執(zhí)行時(shí)間的正確性妻导。

3.接下來就是調(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
  }
}

通過
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback());
把你的回調(diào)添加到Choreographer之中,那么在下一個(gè)frame被渲染的時(shí)候就會(huì)回調(diào)你的callback,執(zhí)行你定義的doFrame操作寿酌,這時(shí)候你就可以獲取到這一幀的開始渲染時(shí)間并做一些自己想做的事情了胰苏。
開源組件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閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件么库,死亡現(xiàn)場離奇詭異,居然都是意外死亡豌蟋,警方通過查閱死者的電腦和手機(jī)廊散,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來梧疲,“玉大人允睹,你說我怎么就攤上這事』系” “怎么了缭受?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長该互。 經(jīng)常有香客問我米者,道長,這世上最難降的妖魔是什么宇智? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任蔓搞,我火速辦了婚禮,結(jié)果婚禮上随橘,老公的妹妹穿的比我還像新娘喂分。我一直安慰自己,他們只是感情好机蔗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布蒲祈。 她就那樣靜靜地躺著甘萧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梆掸。 梳的紋絲不亂的頭發(fā)上扬卷,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音酸钦,去河邊找鬼怪得。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钝鸽,可吹牛的內(nèi)容都是我干的汇恤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拔恰,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼因谎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颜懊,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤财岔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后河爹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匠璧,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年咸这,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夷恍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡媳维,死狀恐怖酿雪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侄刽,我是刑警寧澤指黎,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站州丹,受9級(jí)特大地震影響醋安,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墓毒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一吓揪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧所计,春花似錦磺芭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至讥裤,卻和暖如春放棒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背己英。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工间螟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人损肛。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓厢破,卻偏偏與公主長得像,于是被迫代替她去往敵國和親治拿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摩泪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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