Briefly, Choreographer is about coordinating the timing of animation, input, and drawing. It receives timing pulses from the display subsystem (for example, vSYNC) and then schedules part of the rendering of the next frame.

Custom FrameCallback

FrameCallback is the interface class that interacts with Choreographer and fires when the next frame is rendered. Developers can set up their own FrameCallback. Let’s take a peek into Choreographer’s implementation from the point of view of a custom FrameCallback. Simple implementation is as follows:

private static final String TAG = "Choreographer_test"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageView imageView= (ImageView) findViewById(R.id.iv_anim); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final long starTime=System.nanoTime(); Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { Log.e(TAG,"starTime="+starTime+", frameTimeNanos="+frameTimeNanos+", frameDueTime="+(frameTimeNanos-starTime)/1000000); }}); }}); }Copy the code

In this case, our custom FrameCallback simply prints the time. Enter the following information:

E/Choreographer_test: starTime=232157742945242, frameTimeNanos=232157744964255, frameDueTime=2Copy the code

You can see from log that this frame is processed in about 2ms. Below we peep its concrete realization principle from the source point of view.

Realize the principle of

Key member variables

The constructor

private Choreographer(Looper looper) { mLooper = looper; MHandler = new FrameHandler(looper); MDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper) : null; MLastFrameTimeNanos = long.min_value; GetRefreshRate = (long)(1000000000 / getRefreshRate()); MCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); }}Copy the code

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: // Render the next frame doFrame(system.nanotime (), 0); break; Case MSG_DO_SCHEDULE_VSYNC: // Request VSNYC signal doScheduleVsync(); break; Case MSG_DO_SCHEDULE_CALLBACK: // Execute Callback doScheduleCallback(msg.arg1); break; }}}Copy the code

FrameDisplayEventReceiver

FrameDisplayEventReceiver is a subclass of DisplayEventReceiver, DisplayEventReceiver is receiving VSYNC information layer Java implementation.

public abstract class DisplayEventReceiver { public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {} 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); } } private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }}Copy the code

VSYNC messages are typically generated by hardware interrupts and handled by SurfaceFlinger. The scheduleVsync method is used to request VSNYC signal. After receiving VSYNC information processing, the Native method will call Java layer dispatchVsync method. Final call to FrameDisplayEventReceiver onVsync method, concrete implementation we say again in a minute.

CallbackQueue

CallbackQueue is a single linked list implementation. Each type of callback(CallbackRecord) is stored in its own CallbackQueue in dueTime order. There are four types of callback in Choreographer: Input, Animation, Draw, and one for Animation startup problems.

private final class CallbackQueue { private CallbackRecord mHead; public boolean hasDueCallbacksLocked(long now) { return mHead ! = null && mHead.dueTime <= now; } / / callback, based on the current of time public CallbackRecord extractDueCallbacksLocked (long) {... . Public void addCallbackLocked(long dueTime, Object Action, Object token) {.... . } // Remove callback public void removeCallbacksLocked(Object action, Object token) {.... . }}}Copy the code

2. Process analysis

After outlining Choreographer’s key member variables, let’s return to the postFrameCallback method

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } / / by default CALLBACK_ANIMATION type postCallbackDelayedInternal (CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); }Copy the code

postCallbackDelayedInternal

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; Callbackqueues [callbackType]. AddCallbackLocked (dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else {// After the current time, send MSG_DO_SCHEDULE_CALLBACK, MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); `}}}Copy the code

scheduleFrameLocked

private void scheduleFrameLocked(long now) { .... If (isRunningOnLooperThreadLocked ()) {/ / if the current thread is the UI thread, perform scheduleVsyncLocked request scheduleVsyncLocked VSYNC signal (); MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_VSYNC);} else {MSG = mhandler. obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); }... }Copy the code

ScheduleVsyncLocked eventually call FrameDisplayEventReceiver# scheduleVsync, after receiving the Vsync information, call FrameDisplayEventReceiver# onVsync

FrameDisplayEventReceiver#onVsync

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; public FrameDisplayEventReceiver(Looper looper) { super(looper); } @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { .... . mTimestampNanos = timestampNanos; mFrame = frame; / / the Message of the callback for the current object FrameDisplayEventReceiver, messages are received calls the run method, and then call doFrame method 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); }}Copy the code

doFrame

void doFrame(long frameTimeNanos, int frame) { .... Long intendedFrameTimeNanos = frameTimeNanos; StartNanos = system.nanotime (); // Final Long jitterNanos = startNanos-frameTimenanos; If (jitterNanos >= mFrameIntervalNanos) {final Long skippedFrames = jitterNanos/mFrameIntervalNanos;  if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }... . // Record the current frame information mframeInfo. setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; MLastFrameTimeNanos = frameTimeNanos; } try {// Execute CallBack with priority: CALLBACK_INPUT>CALLBACK_ANIMATION>CALLBACK_TRAVERSAL>CALLBACK_COMMIT 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); }... }Copy the code

doCallbacks

void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); / / from the queue to find the appropriate type of callbacks CallbackRecord object = mCallbackQueues [callbackType] extractDueCallbacksLocked (now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; . . try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); for (CallbackRecord c = callbacks; c ! = null; c = c.next) { .... // Call CallbackRecord's run method 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); }}Copy the code

CallbackRecord#run

Public void run(long frameTimeNanos) {if (token == FRAME_CALLBACK_TOKEN) {// Invoke the doFrame method of custom FrameCallback ((FrameCallback)action).doFrame(frameTimeNanos); } else { ((Runnable)action).run(); }}Copy the code

At this point, the entire invocation flow and principles of Choreographer have been analyzed. For some system calls, such as the invalidate of the View, triggering ViewRootImpl#scheduleTraversals, Finally call Choreographer#postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); Just specify the type of Callbac and the callback handling Runnable. The basic process is the same as custom FrameCallback.

conclusion

  • Try to avoid performing operations on the main thread after performing animation or render operations, and try to avoid sending messages to the main thread looper either before or after

  • Since the custom FrameCallback can be called back when the next frame is rendered, can we apply this principle to the application of frame rate listening? The answer is yes, here is my simple implementation:

1. Customize FrameCallback: FPSFrameCallback

public class FPSFrameCallback implements Choreographer.FrameCallback { private static final String TAG = "FPS_TEST"; private long mLastFrameTimeNanos = 0; private long mFrameIntervalNanos; public FPSFrameCallback(long lastFrameTimeNanos) { mLastFrameTimeNanos = lastFrameTimeNanos; MFrameIntervalNanos = (long)(1000000000/60.0); } @override public void doFrame(long frameTimeNanos) {if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = frameTimeNanos; } final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if(skippedFrames>30){ Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } } mLastFrameTimeNanos=frameTimeNanos; / / register next frame callback Choreographer. GetInstance (). PostFrameCallback (this); }}Copy the code

2. Register with Application

@Override
   public void onCreate() {
       super.onCreate();
       Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
   }Copy the code

3. The test

public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

LOG output is as follows:

I/Choreographer: Skipped 64 frames!  The application may be doing too much work on its main thread.
I/FPS_TEST: Skipped 65 frames!  The application may be doing too much work on its main thread.Copy the code

Basically the same value as the system monitor