This article, the eighth in the Systrace series, provides a brief introduction to Choreographer within Systrace
The “purpose” of this series is to use Systrace as a tool to look at the overall operation of the Android system from another perspective, and to learn about the Framework from another perspective. Maybe you read a lot of articles about the Framework, but can’t remember the code, or the process it runs through. Maybe you can understand it more deeply from the graphical perspective of Systrace.
This article introduces Choreographer, a class that App developers don’t often touch, but is very important in the Android Framework rendering link. Including introduction background, Introduction to Choreographer, partial source parsing, Choreographer and MessageQueue, Choreographer and APM, And some optimization ideas based on Choreographer by mobile vendors
Choreographer was introduced to work with Vsync to provide a stable time for Message processing for upper-level App renderers. This is when Vsync arrives and the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle. At present, most mobile phones have a refresh rate of 60Hz, that is, 16.6ms per refresh. In order to match the refresh frequency of the screen, the system also sets the cycle of Vsync to 16.6ms, 16.6ms per refresh. The Vsync signal wakes up Choreographer to do the drawing operations of your App, and this is where Choreographer is introduced. Understanding Choreographer can also help App developers understand the basics of how each frame of an application works, as well as understanding Message, Handler, Looper, MessageQueue, Measure, Layout, and Draw
Series of articles
- Systrace profile
- Systrace Basics – Systrace prep
- Systrace Basics – Why 60 FPS?
- Systrace basic knowledge – SystemServer interpretation
- Systrace Basics – SurfaceFlinger interpretation
- Systrace Basics – Input interpretation
- Systrace basics – Vsync interpretation
- Systrace Basics – Vsync-App: Choreographer based Rendering mechanics in detail
- Systrace Basics – MainThread and RenderThread interpretation
- Systrace Basics – Triple Buffer interpretation
- Systrace Basics – CPU Info interpretation
The nature of the main thread running mechanism
Before we move on to Choreographer, let’s start by understanding the essence of how Android main threads run. This is essentially the processing of messages. Our operations, including the rendering of each frame, are sent to the main thread’s MessageQueue as a Message. MessageQueue processes the message and continues to wait for the next message, as shown in the following figure
“A MethodTrace diagram for the main thread runtime“
“Systrace diagram of the main thread running“
evolution
Initially, the Android Framework did not have Vsync. Prior to the introduction of Vsync, the Android Framework rendered a frame of Message with no interval between frames. After the last frame was drawn, the next frame’s Message was processed. The problem is that the frame rate can be unstable, either high or low, as shown below
“A MethodTrace diagram of the main thread running without introducing Vsync“
“Systrace diagram of the main thread running without introducing Vsync“
It can be seen that the bottleneck at this time is in the dequeueBuffer, because the screen has a refresh cycle, FB consumes the Front Buffer at a certain rate, so SF consumes the App Buffer at a certain rate. Therefore, the App will get stuck in the dequeueBuffer, which will lead to the unstable acquisition of the App Buffer. It is easy to get stuck and drop frames.
A stable frame rate is a good experience for the user. If you’re playing King of Glory, for example, it’s better to be stable at 50 FPS than if the FPS is constantly changing between 60 and 40.
So the evolution of Android has introduced the mechanism Vsync + TripleBuffer + Choreographer, whose main purpose is to provide a stable frame rate output mechanism so that the software layer and the hardware layer can work together at a common frequency.
Introducing the Choreographer
Choreographer was introduced to work with Vsync to provide a stable time for Message processing for upper-level App renderers. This is when Vsync arrives and the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle. As for the reason why the Vsync cycle is set to 16.6ms (60 FPS), it is because most mobile phone screens have a refresh rate of 60Hz, that is, 16.6ms per refresh. The system also sets the Vsync cycle to 16.6ms to match the screen refresh frequency. Every 16.6ms, Vsync signals wake up Choreographer to start drawing apps. If each Vsync cycle can be rendered, the App will have an FPS of 60, which will feel very smooth to the user. This is the main role introduced into Choreographer
Of course, there are more and more mobile phones using 90Hz or 120Hz refresh rate screen, and the Vsync cycle of the system ranges from 16.6ms to 11.1ms and then to 8ms. The operation in the figure above needs to be completed in a shorter time, and the requirements for performance are getting higher and higher. The details can be seen from the new smooth experience. Ramble on this article at 90Hz
Choreographer profile
Choreographer plays the role of the go-between for Android rendering links
- “On top” : Is responsible for receiving and processing App updates and callbacks until Vsync arrives. For example, processing Input(mainly processing Input events), Animation(Animation related), Traversal(including measure, layout, draw and other operations), judging the frame delay, recording CallBack time, etc
- Start down: requests and receives Vsync signals. Receive Vsync event callback (through FrameDisplayEventReceiver onVsync); Request Vsync (FrameDisplayEventReceiver scheduleVsync).
As you can see from above, Choreographer is playing the role of a tool man. He is important because through Choreographer + SurfaceFlinger + Vsync + TripleBuffer, This ensures that Android App can run at a stable frame rate (currently most of it is 60fps), reducing the discomfort caused by frame rate fluctuations.
Understanding Choreographer can also help App developers understand the fundamentals of how an application runs every frame, It can also deepen the understanding of “Message, Handler, Looper, MessageQueue, Measure, Layout, Draw”. Many “APM” tools also use “Choreographer(using FrameCallback + FrameInfo)” + “MessageQueue (using IdleHandler)” + “Looper (setting custom) MessageLogging) “and once you know what you’re doing, you’ll have a clearer idea of what you’re doing.
And although drawing diagrams is a good way to explain a process, I’m not a big fan of drawing diagrams because I use Systrace and MethodTrace a lot, Systrace is a tool to display the operation of the entire system from left to right (including CPU, SurfaceFlinger, SystemServer, App and other key processes). Using “Systrace” and “MethodTrace” also makes it easy to show key processes. When you are more familiar with the system code, look at Systrace to see how the phone actually works. So, in addition to some network diagrams, I will use Systrace to present the rest of the following articles.
Choreogrepher’s workflow from Systrace’s point of view
The following figure takes sliding desktop as an example. Let’s take a look at a complete preview of sliding desktop from left to right (App process). From left to right in Systrace, each green frame represents a frame, representing the final picture we can see on the phone
- Each gray bar and white bar width in the figure is a Vsync time, that is, 16.6ms
- -> UI Thread -> RenderThread -> SurfaceFlinger(not shown)
- The UI Thread and RenderThread can complete the rendering of an App frame. The rendered Buffer is thrown to SurfaceFlinger for composition, and then we can see the frame on the screen
- As you can see, each frame of the desktop slide takes a very short time (Ui Thread time + RenderThread time), but due to Vsync, each frame is not processed until Vsync
With this overall concept in mind, let’s zoom in on each frame of the UI Thread and see where Choreogrepher is and how Choreogrepher organizes each frame
Choreographer’s workflow
- Choreographer initialization
- Initialize the FrameHandler and bind Looper
- Initialize FrameDisplayEventReceiver, establish communication with SurfaceFlinger Vsync used to receive and request
- Initialize CallBackQueues
- SurfaceFlinger’s appEventThread wakes up to send Vsync, Choreographer callback FrameDisplayEventReceiver onVsync, into SurfaceFlinger doFrame main processing function
- Choreographer. DoFrame calculates the frame drop logic
- Choreographer. DoFrame handles Choreographer’s first callback: input
- Choreographer. DoFrame handles Choreographer’s second callback: animation
- Choreographer. DoFrame handles Choreographer’s third callback: Insets animation
- Choreographer. DoFrame handles Choreographer’s fourth callback: traversal
- Traversal-draw In which UIThread and RenderThread synchronize data
- Choreographer. DoFrame handles Choreographer’s fifth callback: Commit?
- RenderThread processes the drawing data to actually render
- Render the rendered Buffer swap to SurfaceFlinger for composition
“After the initial initialization is complete, subsequent cycles are performed between steps 2-9“
Also attached is the MethodTrace that corresponds to this frame (just preview it here, and see the larger image below).
Here we are from the source point of view, look at the specific implementation
The source code parsing
Here is a brief look from the source code point of view, the source code only extracts some important logic, other logic is excluded, in addition to Native part and SurfaceFlinger interaction part is not included, it is not the focus of this article, interested can follow.
Initialization of Choreographer
Singleton initialization of Choreographer
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue(a) {
// Get the current thread's Looper Looper looper = Looper.myLooper(); . // Construct Choreographer objects Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); if (looper == Looper.getMainLooper()) { mMainInstance = choreographer; } return choreographer; } }; Copy the code
The constructor for Choreographer
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 1. Initialize FrameHandler
mHandler = new FrameHandler(looper);
// 2. Initialize DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; mFrameIntervalNanos = (long) (1000000000 / getRefreshRate()); Initialize the CallbacksQueues 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 void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:// Start rendering the next frame
doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC:/ / request Vsync doScheduleVsync(); break; case MSG_DO_SCHEDULE_CALLBACK:/ / Callback processing doScheduleCallback(msg.arg1); break; } } } Copy the code
Choreographer initializes chains
During the Activity startup process, after onResume is executed, activity.makevisible () is called, and then addView() is called, which leads to the following method
ActivityThread.handleResumeActivity(IBinder, boolean.boolean, String) (android.app)
-->WindowManagerImpl.addView(View, LayoutParams) (android.view)
-->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view)
-->ViewRootImpl.ViewRootImpl(Context, Display) (android.view)
public ViewRootImpl(Context context, Display display) {
. mChoreographer = Choreographer.getInstance(); . } Copy the code
FrameDisplayEventReceiver profile
Vsync registration, apply for and receive by FrameDisplayEventReceiver this class, so we can make a brief introduction. DisplayEventReceiver FrameDisplayEventReceiver inheritance, there are three important methods
- OnVsync — Vsync signal callback
- Run — Performs doFrame
- ScheduleVsync — Request a Vsync signal
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
. @Override
public void onVsync(long timestampNanos, long physicalDisplayId, 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(a) { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } public void scheduleVsync(a) { . nativeScheduleVsync(mReceiverPtr); . } } Copy the code
The registration of Vsync in Choreographer
From the function call stack as you can see, the Choreographer of the inner class FrameDisplayEventReceiver. OnVsync receive Vsync callback, notify the UIThread for data processing.
So FrameDisplayEventReceiver is through what way in callback onVsync Vsync signal arrival time? The answer is FrameDisplayEventReceiver initialization, ultimately through the monitor file handle forms, its corresponding initialization process is as follows
android/view/Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
.} Copy the code
android/view/Choreographer.java
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
Copy the code
android/view/DisplayEventReceiver.java
public DisplayEventReceiver(Looper looper, int vsyncSource) {
. mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
} Copy the code
NativeInit can oneself with the subsequent code, can control the article and the source code, because the space is more, here is not fine to write (www.jianshu.com/p/304f56f5d)… After the logic of this piece is sorted out, it will be updated in another article.
In simple terms, FrameDisplayEventReceiver initialization process, through BitTube (is essentially a socket pair), to deliver Vsync and request event, when SurfaceFlinger received Vsync events, Pass the event via BitTube via appEventThread to DisplayEventDispatcher, which listens to the Vsync event via the BitTube receiver, The callback Choreographer. FrameDisplayEventReceiver. OnVsync, trigger start to paint a frame, the following figure
Choreographer deals with the logic of a frame
Choreographer processing drawing logical core in Choreographer. DoFrame function, from the image below you can see, FrameDisplayEventReceiver. OnVsync post himself, Its run method directly calls doFrame to start the logical processing of a frame
android/view/Choreographer.java
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
. mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } public void run(a) { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } Copy the code
The doFrame function does several things
- Computes the drop frame logic
- Record frame drawing information
- CALLBACK_INPUT, CALLBACK_ANIMATION, CALLBACK_INSETS_ANIMATION, CALLBACK_TRAVERSAL, CALLBACK_COMMIT
Computes the drop frame logic
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
. 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."); } } . } .} Copy the code
Choreographer. DoFrame drop detection is relatively simple. As you can see below, the arrival of the Vsync signal is marked with a start_time and the execution of the doFrame signal with an end_time. That’s dropping frames
Let’s look at the actual case of frame drop in Systrace to see the calculation logic of frame drop
It should be noted that the frame drop calculated by this method is the frame drop of the previous frame, not the frame drop of this frame. This calculation method is flawed, and some frames are not counted
Record frame drawing information
Choreographer FrameInfo is responsible for recording frame drawing information. When doFrame is executed, it records the drawing time of each key node, which can be seen using Dumpsys GfXInfo. Of course Choreographer only records part of it, the rest will be recorded on the HWUi side.
The contents of the records can be seen from the FrameInfo flags. Dumpsys Gfxinfo is used to arrange the data
// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;
// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
// A renderer associated with just a Surface, not with a ViewRootImpl instance. public static final long FLAG_SURFACE_CANVAS = 1 << 2; @LongDef(flag = true, value = { FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS }) @Retention(RetentionPolicy.SOURCE) public @interface FrameInfoFlags {} // The intended vsync time, unadjusted by jitter private static final int INTENDED_VSYNC = 1; // Jitter-adjusted vsync time, this is what was used as input into the // animation & drawing system private static final int VSYNC = 2; // The time of the oldest input event private static final int OLDEST_INPUT_EVENT = 3; // The time of the newest input event private static final int NEWEST_INPUT_EVENT = 4; // When input event handling started private static final int HANDLE_INPUT_START = 5; // When animation evaluations started private static final int ANIMATION_START = 6; // When ViewRootImpl#performTraversals() started private static final int PERFORM_TRAVERSALS_START = 7; // When View:draw() started private static final int DRAW_START = 8; Copy the code
Records from Vsync doFrame function time to markPerformTraversalsStart time
void doFrame(long frameTimeNanos, int frame) {
. mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// Handle CALLBACK_INPUT Callbacks
mFrameInfo.markInputHandlingStart();
// Handle CALLBACK_ANIMATION Callbacks mFrameInfo.markAnimationsStart(); // Handle CALLBACK_INSETS_ANIMATION Callbacks // Handle CALLBACK_TRAVERSAL Callbacks mFrameInfo.markPerformTraversalsStart(); // Handle CALLBACK_COMMIT Callbacks .} Copy the code
Implement Callbacks
void doFrame(long frameTimeNanos, int frame) {
. // Handle CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// Handle CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); // Handle CALLBACK_INSETS_ANIMATION Callbacks doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); // Handle CALLBACK_TRAVERSAL Callbacks doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); // Handle CALLBACK_COMMIT Callbacks doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); .} Copy the code
“The Input callback calls the stack“
“Input” callback is generally perform ViewRootImpl. ConsumeBatchedInputRunnable
android/view/ViewRootImpl.java
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run(a) {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
} void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if(mInputEventReceiver ! =null) { if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos ! = -1) { scheduleConsumeBatchedInput(); } } doProcessInputEvents(); } } Copy the code
The Input time is processed and eventually passed to the DecorView’s dispatchTouchEvent, leading to the familiar distribution of Input events
“Animation calls back to the stack“
The most common thing we see is CALLBACK_ANIMATION when we call View.postonanimation
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); } } Copy the code
PostOnAnimation is not a callback to view. postOnAnimation. It is not a callback to view. postOnAnimation
The call stack is based on the contents of the post, and the following is the fling after the slide.
Choreographer also uses CALLBACK_ANIMATION for FrameCallback
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); } Copy the code
“Traversal call stack“
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled = true;
// To increase the priority, postSyncBarrier is required
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } final class TraversalRunnable implements Runnable { @Override public void run(a) { // Start executing measure, Layout, draw doTraversal(); } } void doTraversal(a) { if (mTraversalScheduled) { mTraversalScheduled = false; // remove the SyncBarrier mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // Get started performTraversals(); } } private void performTraversals(a) { / / measure operation if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } / / layout operations if (didLayout) { performLayout(lp, mWidth, mHeight); } / / the draw operation if(! cancelDraw && ! newSurface) { performDraw(); } } Copy the code
“TraceView example of doTraversal“
Vsync request for the next frame
Animations, slides, and Fling require a continuous, stable frame rate. This involves the request logic of Vsync. In continuous operations such as animations, slides, and flings, each doFrame will trigger the next Vsync request. In this way, continuous Vsync signals can be obtained.
Look at the scheduleTraversals call stack below (where the Vsync request is triggered)
Both the familiar Invalidate and requestLayout trigger the Vsync signal request
Let’s take the Animation as an example. How does the Animation drive the next Vsync to continuously update the screen
ObjectAnimator animation driver logic
android/animation/ObjectAnimator.java
public void start(a) {
super.start();
}
Copy the code
android/animation/ValueAnimator.java
private void start(boolean playBackwards) {
. addAnimationCallback(0); // Add an Animation Callback to start
.}
private void addAnimationCallback(long delay) { . getAnimationHandler().addAnimationFrameCallback(this, delay); } Copy the code
android/animation/AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// post FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
.} // mFrameCallback calls doFrame and posts itself inside private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); if (mAnimationCallbacks.size() > 0) { / / post getProvider().postFrameCallback(this); } } }; Copy the code
Will call postFrameCallback go the mChoreographer postFrameCallback, this will trigger a Choreographer Vsync request logic
android/animation/AnimationHandler.java
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
Copy the code
android/view/Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { / / request Vsync scheduleFrameLocked - > scheduleVsyncLocked - > mDisplayEventReceiver. ScheduleVsync - > nativeScheduleVsync scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } } Copy the code
Through the above Animation. Start setting, using the Choreographer. FrameCallback interface, each frame to request a Vsync “a frame of the Animation process TraceView sample”
Source summary
- Choreographer is thread singleton and must be bound to a Looper because there is an internal Handler that needs to be bound to a Looper, typically the Looper binding for the main thread of your App
- The “DisplayEventReceiver” is an abstract class whose JNI code creates a Vsync listener object for IDisplayEventConnection. In this way, VSYNC interrupt signals from AppEventThread can be passed to Choreographer objects. When the Vsync signal arrives, the DisplayEventReceiver’s onVsync function will be called.
- “DisplayEventReceiver” also has a scheduleVsync function. When an application needs to draw a UI, it first requests a Vsync interrupt and then draws it in the onVsync function that handles the interrupt.
- Choreographer defines a “FrameCallback” “interface” whose doFrame function will be called whenever Vsync arrives. This interface is very helpful for the implementation of Android Animation. Before are their own control of time, now finally have a fixed interruption of time.
- The main function of Choreographer is to invoke callback functions set by the user via postCallback when a Vsync signal is received. Currently, there are five types of callbacks defined:
- CALLBACK_INPUT: processes input events
- CALLBACK_ANIMATION: deals with Animation processing
- CALLBACK_INSETS_ANIMATION: Handles callbacks related to Insets Animation
- “CALLBACK_TRAVERSAL” : handling is related to rendering controls such as UI
- CALLBACK_COMMIT: processes callbacks related to Commit
- The Item initialization of the “ListView” can be obtained in the input or in the animation, depending on how the animation works
- CALLBACK_INPUT and CALLBACK_ANIMATION modify the properties of the view, so they need to be traversal with CALLBACK_ANIMATION first
APM and Choreographer
Due to Choreographer’s location, many of the performance monitoring methods are done using Choreographer. In addition to the built-in frame drop calculation, Choreographer provides FrameCallback and FrameInfo that expose interfaces to apps. App developers can monitor the performance of their own apps through these methods, which are commonly used as follows:
- Take advantage of FrameCallback’s doFrame callback
- Use FrameInfo for monitoring
- Use: ADB shell Dumpsys GFxinfo framestats
- Example: ADB shell dumpsys gfxinfocom.meizu.flyme. Launcher framestats
- Using SurfaceFlinger for monitoring
- Adb shell dumpsys SurfaceFlinger –latency
- Example: the adb shell dumpsys SurfaceFlinger — latency com. Meizu. Flyme. The launcher/com. Meizu. Flyme. The launcher. Launcher# 0
- SurfaceFlinger PageFlip mechanism is used for monitoring
- Adb service Call SurfaceFlinger 1013
- Note: System permission is required
- Choreographer itself calculates the frame drop logic
- BlockCanary is based on Looper performance monitoring
Take advantage of FrameCallback’s doFrame callback
FrameCallback interface
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
Copy the code
Interface to use
Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
Copy the code
Interface processing
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
. postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
Copy the code
TinyDancer uses this method to calculate FPS (github.com/friendlyrob…)
Use FrameInfo for monitoring
adb shell dumpsys gfxinfo framestats
Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms 95th percentile: 36ms 99th percentile: 101ms Number Missed Vsync: 33 Number High input latency: 683 Number Slow UI thread: 273 Number Slow bitmap uploads: 8 Number Slow issue draw commands: 18 Number Frame deadline missed: 287 HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 ---PROFILEDATA--- Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawS tart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,,0,10158315693363,10158315760759,10158315769821,10158316032165,101583 0101831881 426101831881 426922372368477807 16627842101831838 988101831055 915101832387 269101832770 654428 000773 000,,0,10158332799196,10158332868519,10158332877269,10158333137738,101583 0101833036 261101833036 261922372368477807 33780654101833993 206101833078 467101833689 561101833307 061474 000885 000,,0,10158349710238,10158349773102,10158349780863,10158350405863,101583 0101834665 353101834665 353922372368477807 51135967101835360 446101835300 863101835305 654101835814 509471 000836 000,,0,10158365782373,10158365821019,10158365825238,10158365975290,101583 0101836296 729101836296 729922372368477807 66547946101836687 217101836240 706101836429 248101836291 852269 000476 000,Copy the code
Using SurfaceFlinger for monitoring
Command explanation:
- The unit of data is nanosecond, and the time starts from the startup time
- Each command yields 128 lines of frame-related data
Data:
- The first line of data, which indicates the refresh interval refresh_period
- Column 1: The data in this section represents the point in time at which the application drew the image
- Column 2: The vSYNC time before SF(software) submits the frame to H/W(hardware) rendering, i.e. the time stamp submitted to hardware after each frame is drawn. This column is the vsync time stamp
- Column 3: the time point when SF submits the frame to H/W is the time point when H/W receives the data sent by SF and the time point when the drawing is completed.
“Drop frames jank calculation“
Each row can be given a value by the following formula. This value is a standard called JankFlag. If the jankFlag of the current row changes from the JankFlag of the previous row, the frame is called off
ceil((C – A) / refresh-period)
SurfaceFlinger PageFlip mechanism is used for monitoring
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();
final long now = System.nanoTime(); final int frames = pageFlipCount - mLastPageFlipCount; final long duration = now - mLastUpdateTime; mFps = (float) (frames * 1e9 / duration); mLastPageFlipCount = pageFlipCount; mLastUpdateTime = now; reply.recycle(); data.recycle(); Copy the code
Choreographer itself calculates the frame drop logic
SKIPPED_FRAME_WARNING_LIMIT default is 30, from the debug. Choreographer. Skipwarning this property control
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.");
} } Copy the code
BlockCanary
Blockcanary calculates performance monitoring using Looper’s Message mechanism. It records before and after each Message in MessageQueue to monitor performance
android/os/Looper.java
public static void loop(a) {
. for (;;) {
. // This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging; if(logging ! =null) { logging.println(">>>>> Dispatching to " + msg.target + "" + msg.callback + ":" + msg.what); } msg.target.dispatchMessage(msg); if(logging ! =null) { logging.println("<<<<<< Finished to " + msg.target + "" + msg.callback); } } } Copy the code
MessageQueue and Choreographer
If a Barrier is inserted into an enqueueBarrier, all synchronous messages will be blocked by the Barrier. Asynchronous messages do not matter until we call removeBarrier to remove the Barrier, and messages default to synchronous messages unless we call setAsynchronous of Message, which is hidden. The enqueueMessage of the Handler will call the setAsynchronous asynchronous Message to make the Message asynchronous only when the Handler is initialized. You can see this in the code above for handler. enqueueMessage.
If a Barrier is not set, asynchronous messages are no different from synchronous messages and can be removed by removeSyncBarrier
An example of SyncBarrier used in Choreographer
ScheduleTraversals postSyncBarrier
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled = true;
// To increase the priority, postSyncBarrier is required
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } Copy the code
RemoveSyncBarrier when doTraversal
void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// remove the SyncBarrier
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// Get started performTraversals(); } } Copy the code
Choreographer Post Messages will be set to Asynchronous so that the messages in Choreographer will have a higher priority,
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
Copy the code
Vendors to optimize
System manufacturers can directly modify the source code, but also use the convenience of this aspect, do some functions and optimization, but because of the problem of confidentiality, the code is not directly put up, I can roughly say about the idea, interested in private discussion
Movement event optimization
Choreographer itself does not have input messages, but after modifying the source code, input messages can be sent directly to Choreographer. With these input messages, Choreographer can do things like respond ahead of time, Not to wait Vsync
Background animation optimization
When an Android App goes back into the background, as long as it doesn’t get killed, don’t be surprised what it does, because that’s Android. Some apps continue to call Choreographer’s Animation Callback even after they have fallen into the background, and the execution of this Callback is completely meaningless and unknown to the user, but is CPU intensive.
So Choreographer is going to optimize for this situation by preventing non-conforming apps from continuing useless operations in the background
Frame rendering optimization
As with motion event optimization, with Input event information, in some scenarios we can tell SurfaceFlinger to do the synthesis without waiting for Vsync
Application startup optimization
We said earlier that all operations on the main thread are given to Message, and that if non-important messages are queued at the end of an operation, that operation will be affected. By rearranging MessageQueue, the important startup messages related to the startup are placed in front of the queue when the application is started to speed up startup
High frame rate optimization
On phones with 90 or 120 FPS, the Vsync interval goes from 16.6ms to 11.1ms or 8ms, which brings huge performance and power challenges. How to perform the necessary operations of rendering in one frame is something that phone manufacturers have to think about and optimize:
- Super App performance and optimization
- Game high frame rate cooperation
- Logic for switching between 120fps, 90FPS and 60fps
The resources
- www.jianshu.com/p/304f56f5d…
- Gityuan.com/2017/02/25/…
- Developer.android.com/reference/a…
- www.jishuwen.com/d/2Vcc
- Juejin. Cn/post / 684490…
- Android development master class
About me
Small factory system r&d engineer, more information can click on me, I really hope to communicate with you, common progress.
❝
“A person can go faster, a group can go farther“
❞
This article is formatted using MDNICE