1. First introduce some basic knowledge
1. Refresh Rate:
The refresh rate represents the number of times the screen refreshes in one second, expressed in Hertz. Hertz is a unit of frequency, the number of vibrations in one second. This refresh rate depends on the hardware fixed parameters. This value is generally 60Hz. That is, refresh the screen every 16.66ms.
2. Frame Rate:
The frame rate represents the number of frames the GPU draws in one second. For example, 30FPS and 60FPS. Frame Per Second.
3. If the two devices run independently, if the refresh rate and frame rate are not synchronized, the following two problems will be caused.
If the frame rate is higher than the refresh rate and the production frequency is greater than the display frequency, it will cause the display of the screen image to jump and frame skip.
If the frame rate is less than the refresh rate and the production frequency is less than the display frequency, it will cause the display of the screen image to be interrupted and frame drops.
To solve the above problem, Projectbuffer was introduced in version 4.1.
ProjectBuffer refactored the Android Display system and introduced three core elements, namely Vsync, TripleBuffer and Choreographer.
Where Vsync is Vertical Synchronization Vertical Synchronization is the abbreviation. It is a technology that has been widely used on PCs for a long time.
The introduction is Vsync to control the CPUGPU drawing and screen refresh synchronization.
The introduction of choreography by choreography is mainly to cooperate with Vsync to provide a stable opportunity for the rendering of the upper-level app. When Vsync arrives, Choreographer can uniformly manage the execution of application input, animation, drawing and other tasks based on the Vsync signal. Choreographer is like a conductor, controlling the drawing of the UI, so he named it the choreographer.
2. How does Choreographer run in android source code
1. First create the Choreographer object in the ViewRootImpl constructor
public ViewRootImpl(Context context, Display display) { mChoreographer = (); } public static Choreographer getInstance() { return (); }
When calling get, if null, the initialValue() method will be called. And bind the Choreographer instance to ThreadLocal.
private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { //Because handler communication will be used later, there must be a Looper loop Looper looper = (); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); //If it is the main thread, assign the choreographer to mMainInstance if (looper == ()) { mMainInstance = choreographer; } return choreographer; } };
2. Look at the Choreographer constructor
mLastFrameTimeNanos: Records the time of the previous frame being drawn.
mFrameIntervalNanos: The time interval for the screen to draw a frame, this is the nanosecond value. If the screen refresh rate is 60Hz, the time interval for refreshing one frame is 16.66...ms.
private Choreographer(Looper looper, int vsyncSource) { // Send a Looper in, mLooper = looper; // Used to process messages. mHandler = new FrameHandler(looper); //USE_VSYNC Whether to use Vsync //boolean USE_VSYNC = ("", true); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; //The time of the previous frame drawing mLastFrameTimeNanos = Long.MIN_VALUE; //1 second is 1000 milliseconds, 1 millisecond is 1000 microseconds, 1 microsecond is 1000 nanoseconds //1 second is the ninth power nanosecond of 1*1000*1000=10=10 //Draw the time interval of one frame---- nanoseconds. If it is 60Hz, then the time for refreshing a frame to display is 16.66 milliseconds. mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); //Initialize the callback queue, and the Runnable will be removed from this callback queue and execute the run method. mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } }
Get the refresh rate of the screen:
//The refresh rate of the screen, how many times can the screen be refreshed in one second, usually 60Hzprivate static float getRefreshRate() { DisplayInfo di = ().getDisplayInfo( Display.DEFAULT_DISPLAY); return ().getRefreshRate(); }
3. The initialization work is completed, so how does Choreographer run and where is the entry function?
For UI drawing, it is the entry in the scheduleTraversals() method of RootViewImpl.
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //Send a barrier message mTraversalBarrier = ().getQueue().postSyncBarrier(); //Note the first parameter CALLBACK_TRAVERSAL, the type of the callback function. //mTraversalRunnable The runnable to be executed by the callback function. //The third parameter token passed a null ( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }
//The first parameter callbackType has five types, and these callbacks are in sequence.
1.CALLBACK_INPUT input callback, run first
2.CALLBACK_ANIMATION animation callback, when you apply the animation principle, you will see
3.CALLBACK_INSETS_ANIMATION inset and update related animations, after running the above two callbacks,
4.CALLBACK_TRAVERSAL traversal callbacks, used to handle layout and drawing
5.CALLBACK_COMMIT Commit callback, after Traversal draws the callback.
Next, let’s look at the postCallbackDelayedInternal method
The second parameter is the mTraversalRunnable above.
The time of delay for the fourth parameter, here the delay time is 0, there is no delay
So this method takes the first branch of if judgment
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { final long now = (); final long dueTime = now + delayMillis; //Add runnable to callback queue mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); It's passed from abovedelayMillisyes0,So go to the first branch。 if (dueTime <= now) { scheduleFrameLocked(now); } else { // If there is a delay, a delayed asynchronous message is sent. This message was introduced in the handler synchronization barrier article Message msg = (MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; (true); (msg, dueTime); } } }
private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; //If using vertical synchronization if (USE_VSYNC) { //Judge whether it is running in the main thread. If so, call scheduleVsyncLocked() directly //If it is run on a child thread, scheduleVsyncLocked() will also be called by sending handler if (isRunningOnLooperThreadLocked()) {//() == mLooper scheduleVsyncLocked(); } else { Message msg = (MSG_DO_SCHEDULE_VSYNC); (true); (msg); } }else{ final long nextFrameTime = ( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); Message msg = (MSG_DO_FRAME); (true); (msg, nextFrameTime); } } }
scheduleVsyncLocked() method.
private void scheduleVsyncLocked() { //Calling the method of the parent class DisplayEventReceiver (); }
nativeScheduleVsync() method will be called in scheduleVsync() method. This is a native method. After the native layer is executed, it will call back to the java layer method dispatchVsync()
scheduleVsync: Request a Vsync signal from the native layer.
dispatchVsync: After requesting the Vsync signal, execute the Java layer UI drawing and rendering logic.
public void scheduleVsync() { if (mReceiverPtr == 0) { (TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { // Call native method //Calling the Native method to request a Vsync signal, and then calling back the dispatchVsync method of the java layer from the native layer will be called back from the native layer. nativeScheduleVsync(mReceiverPtr); } }
timestampNanos: A time stamp passed from the Native layer, the time Vsync sent from the native layer.
// Called from native code. //Calling back the dispatchVsync method of java layer from native layerprivate void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) { onVsync(timestampNanos, physicalDisplayId, frame); }
Here another asynchronous message is sent, and (mHandler, this); the second parameter is a callBack callback. Therefore, without a handler, this callback function will be executed. But this is passed, so this run method will be executed. This is an instance of FrameDisplayEventReceiver, initialized in the constructor of Choreographer.
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { mTimestampNanos = timestampNanos; mFrame = frame; //Get message Added a callback function, this, the run method will be called Message msg = (mHandler, this); (true); (msg, timestampNanos / TimeUtils.NANOS_PER_MS); }
In the run method of FrameDisplayEventReceiver, the doFrame method called
@Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); }
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (!mFrameScheduled) { return; // no work to do } // The time when the sync signal is sent, long intendedFrameTimeNanos = frameTimeNanos; //Current time startNanos = (); //The time difference obtained by subtracting the two is the time spent on underlying message communication and callback final long jitterNanos = startNanos - frameTimeNanos; //If this time difference is greater than the time interval of one frame. if (jitterNanos >= mFrameIntervalNanos) { // Calculate how many frames have been skipped final long skippedFrames = jitterNanos / mFrameIntervalNanos; //Note the following line days. If the frame skip is greater than 30 frames, the system will print the following line log. If you do too much work in the main thread, it will cause UI lag. if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { (TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } //Take the modulus and the value obtained is the extra time of one frame final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; } //Subtract the extra time with the current time, which is the time to draw the next frame //Carry out the correction of the drawing time to ensure that the drawing time interval is mFrameIntervalNanos frameTimeNanos = startNanos - lastFrameOffset; } //If the time passed by the bottom layer is less than the time drawn in the previous frame, under normal circumstances, frameTimeNanos is greater than the time drawn in the previous frame. if (frameTimeNanos < mLastFrameTimeNanos) { //Skip this drawing and request the next frame time scheduleVsyncLocked(); return; } //The above judgments are all for controlling the frequency of drawing. if (mFPSDivisor > 1) { long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { scheduleVsyncLocked(); return; } } (intendedFrameTimeNanos, frameTimeNanos); //Reset the flag bit, you can enter scheduleFrameLocked again mFrameScheduled = false; //Record the time passed by the bottom layer as the time of this drawing, that is, the time of the previous frame being drawn when the next frame is transmitted. mLastFrameTimeNanos = frameTimeNanos; } try { (frameTimeNanos / TimeUtils.NANOS_PER_MS); (); //Chang callbacks of callBack according to Choreographer's CallBack type. //enter doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); (); //Animation doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); (); //Interface drawing doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); //commit doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { (); (Trace.TRACE_TAG_VIEW); } }
This is a very important method.
Through the logic in this method, we can see that Choreographer controls the rhythm and frequency of the UI drawing of the App layer.
Then some columns of doCallBacks functions will be executed in order.
First, CallBackRecord will be taken out from the linked list according to the callbackType. Then iterate over CallBackRecord and call its run method.
void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = (); //According to callbacktype, get CallbackRecord from the linked list callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; for (CallbackRecord c = callbacks; c != null; c = ) { //Execute the run method of CallbackRecord (frameTimeNanos); } } }
According to the token, distinguish whether it is FrameCallback type or Runnable.
The token here is null, so the else branch will be executed.
This action is mTraversalRunnable, which calls the run method of mTraversalRunnable.
(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
public void run(long frameTimeNanos) { if (token == FRAME_CALLBACK_TOKEN) { ((FrameCallback)action).doFrame(frameTimeNanos); } else { ((Runnable)action).run(); } }
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }
DoTraversal() is executed in its run method.
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; //Delete the barrier message ().getQueue().removeSyncBarrier(mTraversalBarrier); //Call measurement, layout and drawing methods performTraversals(); } }
The performTraversals() method will be called
performMeasure, performLayout, performDraw, measure, lay out, and draw the View.
This is the end of this article about the detailed analysis of Android Choreographer source code. For more related Android Choreographer content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!