[Framework] Android Choreographer working principle

Choreographer

Introduction

ChoreographerReceive signals ( FrameDisplayEventReceiversent 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.VsyncSurfaceFlingerVsyncInputEventViewRootImplAnimationView#postOnAnimationRunnableTraversalViewRootImpl
ViewRootImplViewRootImplChoreographerTraversalVsync
ActivityresumeViewRootImplDialogViewRootImplChoreographer

Subsequent code reading is based on API 31

GetVsync

The instance will be initialized in Choreographerthe constructor FrameDisplayEventReceiverto obtain Vsyncthe signal. The onVsyncmethod 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 onVsyncthe 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 Vsynchas 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 doFramemethods. There is one more thing to remind us here. onVsyncThe signal callback is not called every time. We need to manually call FrameDisplayEventReceiver#scheduleVsync()the method before the next Vsyncsignal 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.InputEventCALLBACK_INPUTAnimationCALLBACK_ANIMATIONCALLBACK_INSETS_ANIMATIONTraversalCALLBACK_TRAVERSALCallbackQueue

Next, take a look at the entry function for task processing mentioned above doFrame.

Before starting the task, Vsyncthe 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 Vsyncsignal 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 mFPSDivisorlimit 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);

        //...
    }

mFrameInfoA lot of useful information is recorded in , including the Vsyncstart 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_TRAVERSALand the final actual task execution calls doCallbacksthe 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 mCallbackQueuesfrom , and then filter out expired callbacks based on time. If it is CALLBACK_COMMIT( Vsyncthe 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 runmethod 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();
        }
    }
}

CallbackRecordThe 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 invalidateChildthe method of parent. We continue to follow the method ViewGroupof invalidateChildand 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#invalidateChildthe method, it will go around and call scheduleTraversalthe 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 mTraversalRunnableto CALLBACK_TRAVERSALthe queue, waiting for Vsyncexecution when the next signal comes.

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

TraversalRunnablerunCall the method directly in doTraversal, this method is the method that triggers the entire ViewTree measure, layout and draw.

In Choreographer#postCallbackthe method, it will finally be called into postCallbackDelayedInternalthe 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 Vsyncsignal; if the execution time has not yet arrived, then send a delayed task through the handler, and then request Vsyncthe signal when the time is up.

View animation

Let's look directly at View#postOnAnimationthe 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 Choreographeradd CALLBACK_ANIMATIONtasks 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.
insert image description here
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

insert image description here
2. Source code analysis collection
insert image description here

3. The collection of open source frameworks
insert image description here
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↓↓↓

Guess you like

Origin blog.csdn.net/m0_56255097/article/details/132759529