Choreographer
Introduction
Choreographer
Receive signals ( FrameDisplayEventReceiver
sent by ) through the internal and process them uniformly after receiving (some events are processed here and sent to ), (if the method is called, it will be called back directly to the corresponding ) and (that is, Corresponding to View's layout, measure and draw processes, events are delivered to , and then delivered to all View) and other tasks in turn through bubbling. Here we take the drawing of View as an example to describe the process: If the UI of the View needs to be modified, we will call the View#invalidate() method to request redrawing. This method will usually call the parent's invalidate method, and finally call the invalidate in Method, in will directly request to add a type task in, this task will be executed after the next signal comes (if there are other tasks at this time, it will also be executed), and finally the drawing is completed. An instance will be created during the life cycle of ( it may also be created in other places related to the UI, for example .), and the instance will be obtained in the constructor , which is a singleton object.Vsync
SurfaceFlinger
Vsync
InputEvent
ViewRootImpl
Animation
View#postOnAnimation
Runnable
Traversal
ViewRootImpl
ViewRootImpl
ViewRootImpl
Choreographer
Traversal
Vsync
Activity
resume
ViewRootImpl
Dialog
ViewRootImpl
Choreographer
Subsequent code reading is based on API 31
GetVsync
The instance will be initialized in Choreographer
the constructor FrameDisplayEventReceiver
to obtain Vsync
the signal. The onVsync
method is the vertical synchronization signal callback. This method contains some important information of the frame, including the timestamp of the Vsync signal, frame number, frame interval, frame Draw the completed deadline:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, 0);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync " + vsyncEventData.id);
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
In onVsync
the callback method, first check the timestamp of the signal. If it is greater than the current time, discard the current signal. If the last signal Vsync
has not been processed, it will be discarded directly. Then add it to the task queue through the handler (created during initialization, working on the main thread). Note that the msg here is asynchronous (the processing priority is higher). The final task processing is in the method, the entry point of processing run
. Functions are doFrame
methods. There is one more thing to remind us here. onVsync
The signal callback is not called every time. We need to manually call FrameDisplayEventReceiver#scheduleVsync()
the method before the next Vsync
signal callback is given to us.
perform tasks
Before talking about executing tasks, let's first talk about where and how these tasks waiting to be executed are placed. They are all placed in according to classification . In this array, the task queues of ( ), ( and ), ( ) private final CallbackQueue[] mCallbackQueues;
are placed in sequence . The implementation in is a linked list, and the tasks that are executed will be removed from the linked list. I will not post their specific codes. If you are interested, take a look.InputEvent
CALLBACK_INPUT
Animation
CALLBACK_ANIMATION
CALLBACK_INSETS_ANIMATION
Traversal
CALLBACK_TRAVERSAL
CallbackQueue
Next, take a look at the entry function for task processing mentioned above doFrame
.
Before starting the task, Vsync
the difference between the current time and the generated time will be compared with the frame interval. If it is greater than the frame interval, it means that the processing time of the previous frame is too long, resulting in dropped frames:
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
//...
}
The above code block calculates the dropped frame and recalculates the frame time based on the offset of the dropped frame.
The following code adds two logics to skip frame drawing. After skipping this drawing, the scheduleVsyncLocked()
next Vsync
signal will be requested again through the method.
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
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.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
}
//...
}
If the current frame timestamp is smaller than the last frame timestamp, skip this drawing (usually caused by the previous frame drawing timeout); there is also a way to mFPSDivisor
limit the refresh time interval, so that the frame rate can be controlled. Usually this value is 1, which means no limit.
The following is the execution of the task:
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
//...
}
mFrameInfo
A lot of useful information is recorded in , including the Vsync
start time, frame interval, deadline for completion of drawing, time consumption of each stage and other information. These data are very useful when analyzing application performance.
Tasks are executed in sequence CALLBACK_INPUT
, CALLBACK_ANIMATION
, CALLBACK_INSETS_ANIMATION
, CALLBACK_TRAVERSAL
and the final actual task execution calls doCallbacks
the method:
void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
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 * frameIntervalNanos) {
final long lastFrameOffset = jitterNanos % frameIntervalNanos
+ frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (frameIntervalNanos * 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);
}
}
First, get the desired task queue mCallbackQueues
from , and then filter out expired callbacks based on time. If it is CALLBACK_COMMIT
( Vsync
the last type of task), it will also record some timeout logs and update some status-related variables. . Then the tasks are executed in sequence. The class corresponding to Callback is CallbackRecord
, and the corresponding run
method is called during execution. Finally recycle these Callbacks.
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
CallbackRecord
The code in is really very simple, so I won’t go into details.
Let’s take a look at View’s request drawing and animation
View request drawing
As we all know, if you need to update the UI of the View, you need to call View#invalidate()
the method. The last few jumps will execute the following code:
// ...
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// ...
He will call invalidateChild
the method of parent. We continue to follow the method ViewGroup
of invalidateChild
and find that it will also call the method of its parent invalidateChild
, which means that it will call it layer by layer until the top-level parent. Here I will directly conclude that the top-level The parent is ViewRootImpl
.
In ViewRootImpl#invalidateChild
the method, it will go around and call scheduleTraversal
the method at the end.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
After layers of calls, we finally saw what we are familiar with Choreographer
. He added a barrier to the Looper queue of the main thread (to increase the priority of the task), and then added it mTraversalRunnable
to CALLBACK_TRAVERSAL
the queue, waiting for Vsync
execution when the next signal comes.
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
TraversalRunnable
run
Call the method directly in doTraversal
, this method is the method that triggers the entire ViewTree measure, layout and draw.
In Choreographer#postCallback
the method, it will finally be called into postCallbackDelayedInternal
the method:
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);
}
}
}
First, add the task to the queue corresponding to the callbackType. If the requested time is less than or equal to the current time, directly request the next Vsync
signal; if the execution time has not yet arrived, then send a delayed task through the handler, and then request Vsync
the signal when the time is up.
View animation
Let's look directly at View#postOnAnimation
the method:
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().post(action);
}
}
No detours at all, just Choreographer
add CALLBACK_ANIMATION
tasks directly to .
If you have no direction, here is a set of "Advanced Notes on the Eight Modules of Android" written by a senior architect at Alibaba to help you systematically organize messy, scattered, and fragmented knowledge, so that you can systematically and efficiently Master the various knowledge points of Android development.
Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are strictly arranged according to the knowledge system.
Full set of video materials:
1. Interview collection
2. Source code analysis collection
3. The collection of open source frameworks
welcomes everyone to support with one click and three links. If you need the information in the article, just click on the CSDN official certification WeChat card at the end of the article to get it for free↓↓↓