Lifelong learning with you, this is Android programmer
This article mainly introduces some knowledge points in Android development. By reading this article, you will gain the following contents:
1, The essence of the main thread running mechanism
2, Introduction to Choreographer
3, Source code analysis of Choreographer
4, APM and Choreographer
5, MessageQueue and Choreographer
6, Mobile phone manufacturer optimization
This article introduces a class Choreographer that App developers don't often contact but is very important in the Android Framework rendering link. It includes the introduction background of Choreographer, the introduction of Choreographer, some source code analysis, Choreographer and MessageQueue, Choreographer and APM, as well as some optimization ideas of mobile phone manufacturers based on Choreographer.
The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable opportunity for Message processing for the rendering of upper App, that is, when Vsync comes, 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, refresh every 16.6ms. In order to match the refresh rate of the screen, the system also sets the cycle of Vsync to 16.6ms. Each 16.6ms, the Vsync signal wakes up Choreographer to Draw the App. This is the main function of introducing Choreographer Understanding Choreographer can also help App developers know the basic principle of each frame of the program, and deepen their understanding of Message, Handler, Looper, MessageQueue, Measure, Layout and Draw
1, The essence of main thread running mechanism
Before talking about Choreographer, let's take a look at the essence of Android main thread running, which is actually the processing process of Message. Our various operations, including the rendering operation of each frame, are sent to the main thread's MessageQueue in the form of Message. After processing the Message, the MessageQueue continues to wait for the next Message, as shown in the figure below
MethodTrace diagram
Systrace diagram
1. Evolution
The Android version before the introduction of Vsync renders a frame of related messages with no interval in the middle. After the previous frame is drawn, the Message of the next frame begins to be processed immediately. The problem is that the frame rate is unstable, which may be high or low, as shown in the following figure
MethodTrace diagram
Systrace diagram
For users, a stable frame rate is a good experience. For example, when you play king glory, users feel better when it is stable at 50 fps than when fps changes frequently between 60 and 40.
Therefore, in the evolution of Android, the mechanism of "Vsync + TripleBuffer + Choreographer" is introduced. Its main purpose is to provide a stable frame rate output mechanism so that the software layer and hardware layer can work together at a common frequency.
##2. Introduce Choreographer
The introduction of Choreographer is mainly to cooperate with Vsync to provide a stable opportunity for Message processing for the rendering of upper App, that is, when Vsync comes, the system controls the timing of each frame drawing operation by adjusting the Vsync signal cycle As for why the Vsync cycle is 16.6ms (60 fps), the reason is that most mobile phone screens are refreshed at a refresh rate of 60Hz, that is, once every 16.6ms. In order to match the refresh frequency of the screen, the system also sets the Vsync cycle to 16.6ms. Every 16.6ms, the Vsync signal wakes up Choreographer to draw the App, If the application can be rendered in every Vsync cycle, the fps of the application is 60, which makes the user feel very smooth. This is the main function of introducing Choreographer.
Of course, at present, more and more mobile phones use 90Hz refresh rate screens. The Vsync cycle has changed from 16.6ms to 11.1ms. The operations in the above figure should be completed in a shorter time, and the requirements for performance are becoming higher and higher. See the details New smooth experience, 90Hz rambling This article
2, Introduction to Choreographer
Choreographer plays a connecting role in the Android rendering link
- Bearing:
Be responsible for receiving and processing various update messages and callbacks of the App, and handle them uniformly when Vsync arrives. For example, centralized processing of Input (mainly the processing of Input events), animation (animation related), traversal (including measure, layout, draw and other operations), judgment of frame jamming, recording of CallBack time, etc. - Start:
Be responsible for requesting and receiving Vsync signals. Receive Vsync event callback (via FrameDisplayEventReceiver.onVsync); Request Vsync(FrameDisplayEventReceiver.scheduleVsync)
It can be seen from the above that Choreographer plays the role of a tool man. The reason why he is important is that through the top-down mechanism of Choreographer + SurfaceFlinger + Vsync + TripleBuffer, Android App can run at a stable frame rate (most of which are 60fps at present) to reduce the discomfort caused by frame rate fluctuation
Understanding Choreographer can also help App developers know the basic principle of each frame of the program, and deepen their understanding of Message, Handler, Looper, MessageQueue, Measure, Layout and Draw, Many APM tools also use the combination of "Choreographer" (using FrameCallback + FrameInfo) + MessageQueue (using IdleHandler) + Looper (setting custom MessageLogging). After understanding these in depth, you can do optimization, and your mind will be clearer.
In addition, although drawing is a good way to explain the process, I personally don't like drawing very much, because systrace and MethodTrace are often used. Systrace is a tool to show the operation of the whole system from left to right (including cpu, SurfaceFlinger, SystemServer, App and other key processes), Key processes can also be easily demonstrated using # systrace # and # MethodTrace #. When you are familiar with the system code, you can look at systrace to correspond to the actual situation of mobile phone operation. So in addition to some network diagrams, I will show more of the following articles with systrace
1. From the perspective of Systrace, choreographer's workflow
The following figure takes the sliding desktop as an example. Let's take a look at a complete preview (App process) of the sliding desktop from left to right. We can see that from left to right in Systrace, each green frame represents one frame, which represents the final picture we can see on the mobile phone
- The width of each gray bar and white bar in the figure is a Vsync time, that is, 16.6ms
- Processing flow of each frame: callback after receiving Vsync signal - > UI thread – > renderthread – > surfaceflinger (not shown in the figure)
- UI Thread and RenderThread can complete the rendering of a frame of App, and the rendered Buffer is thrown to SurfaceFlinger to synthesize, and then we can see this frame on the screen
- It can be seen that each frame of desktop sliding takes a short time (Ui Thread time + RenderThread time). However, due to the existence of Vsync, each frame will not be processed until Vsync
With the above overall concept, let's enlarge each frame of UI Thread to see the location of choreographer and how choreographer organizes each frame
##2.Choreographer workflow
- Choreographer initialization
- Initialize FrameHandler and bind Looper
- Initialize FrameDisplayEventReceiver and establish communication with SurfaceFlinger to receive and request Vsync
- Initialize CallBackQueues
- The appEventThread of SurfaceFlinger wakes up and sends Vsync, and the Choreographer calls back framedisplayeventreceiver Onvsync, enter the main processing function doFrame of SurfaceFlinger
- Choreographer.doFrame calculates the frame logic
- Choreographer. The doframe handles the first callback: input of the choreographer
- Choreographer. The doframe handles the second callback: animation of the choreographer
- Choreographer.doFrame handles the third callback of Choreographer: insets animation
- Choreographer. The doframe handles the fourth callback of the Choreographer: traversal
- Synchronous data of UIThread and RenderThread in traversal draw
- Choreographer.doFrame handles the fifth callback: commit of choreographer
- RenderThread processes the drawing data and really renders
- Give the rendered Buffer swap to SurfaceFlinger for synthesis
After the initialization of the first step is completed, the subsequent steps will cycle between steps 2-9
At the same time, the MethodTrace corresponding to this frame is also attached (just preview here, and there will be a detailed large picture below)
Let's look at the specific implementation from the perspective of source code
#3, Source code analysis of Choreographer
Let's take a brief look from the perspective of the source code. The source code only extracts some important logic, while other logic is eliminated. In addition, the interaction between Native and SurfaceFlinger is not included, which is not the focus of this article. Those who are interested can follow it by themselves.
1. 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() { // Gets the Looper of the current thread Looper looper = Looper.myLooper(); ...... // Constructing Choreographer objects Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); if (looper == Looper.getMainLooper()) { mMainInstance = choreographer; } return choreographer; } };
- 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()); //3\. Initialize CallbacksQueues mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); } ...... }
- 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://Process Callback doScheduleCallback(msg.arg1); break; } } }
##2. Choreographer initialization chain
In the process of Activity startup, after onResume is executed, Activity. Will be called Makevisible(), and then call addView(), and the layer by layer call will enter 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(); ...... }
3. Introduction to framedisplayeventreceiver
Vsync is registered, applied and received through the FrameDisplayEventReceiver class, so you can briefly introduce it first. FrameDisplayEventReceiver inherits DisplayEventReceiver and has three important methods
- onVsync – Vsync signal callback
- run – execute doFrame
- scheduleVsync – request 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() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } public void scheduleVsync() { ...... nativeScheduleVsync(mReceiverPtr); ...... } }
4. Registration of Vsync in choreographer
As can be seen from the function call stack below, the internal class framedisplayeventreceiver. Of Choreographer Onvsync is responsible for receiving Vsync callback and notifying UIThread to process data.
How does the FrameDisplayEventReceiver call back onVsync when the Vsync signal arrives? The answer is that when the FrameDisplayEventReceiver is initialized, it is finally in the form of listening file handle. The 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; ...... }
android/view/Choreographer.java
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { super(looper, vsyncSource); }
android/view/DisplayEventReceiver.java
public DisplayEventReceiver(Looper looper, int vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource); }
The subsequent code of nativeInit can be followed by yourself. You can compare this article with the source code. Due to the large space, I won't write it in detail here( https://www.jianshu.com/p/304f56f5d486 ), after sorting out the logic of this section, it will be updated in another article.
In short, during the initialization of framedisplayeventreceiver, Vsync events are transmitted and requested through BitTube (essentially a socket pair). After the surface flinger receives the Vsync event, this event is transmitted to the DisplayEventDispatcher through BitTube through appEventThread, The DisplayEventDispatcher will call back choreographer.com after listening to the Vsync event through the receiving end of BitTube FrameDisplayEventReceiver. Onvsync, trigger to start the drawing of one frame, as shown in the following figure
##5.Choreographer handles the logic of a frame
The logical core of Choreographer's processing and drawing is in Choreographer In the doFrame function, you can see from the following figure that framedisplayeventreceiver Onvsync post itself, and its run method directly calls the 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() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); }
The doFrame function mainly does the following things
- Calculate frame logic
- Record frame drawing information
- Execute CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_INSETS_ANIMATION,CALLBACK_TRAVERSAL,CALLBACK_COMMIT
##6. Calculate 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."); } } ...... } ...... }
Choreographer. The frame drop detection of doFrame is relatively simple. As can be seen from the figure below, when the Vsync signal arrives, it will mark a start_time, mark an end when executing the doFrame_ Time, the difference between these two times is the Vsync processing delay, that is, the frame drop
We look at the calculation logic of frame dropping based on the actual situation of frame dropping of Systrace
It should be noted here 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, which will lead to some frame drop not being calculated
##7. Record frame drawing information
FrameInfo in Choreographer is responsible for recording frame drawing information. When doFrame is executed, it will record the drawing time of each key node. We can see it by using dumpsys gfxinfo. Of course, Choreographer only records a part, and the rest is recorded in hwui.
The contents of records can be seen from the signs of FrameInfo. When we look at dumpsys gfxinfo later, the data is arranged according to this
// 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;
The doFrame function records the time from Vsync time to markperformtraversalstart
void doFrame(long frameTimeNanos, int frame) { ...... mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); // Process CALLBACK_INPUT Callbacks mFrameInfo.markInputHandlingStart(); // Process CALLBACK_ANIMATION Callbacks mFrameInfo.markAnimationsStart(); // Process CALLBACK_INSETS_ANIMATION Callbacks // Process CALLBACK_TRAVERSAL Callbacks mFrameInfo.markPerformTraversalsStart(); // Process CALLBACK_COMMIT Callbacks ...... }
##8. Execute Callbacks
void doFrame(long frameTimeNanos, int frame) { ...... // Process CALLBACK_INPUT Callbacks doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); // Process CALLBACK_ANIMATION Callbacks doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); // Process CALLBACK_INSETS_ANIMATION Callbacks doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); // Process CALLBACK_TRAVERSAL Callbacks doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); // Process CALLBACK_COMMIT Callbacks doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); ...... }
Input callback call stack
input callback is generally to execute viewrootimpl ConsumeBatchedInputRunnable
android/view/ViewRootImpl.java
final class ConsumeBatchedInputRunnable implements Runnable { @Override public void run() { doConsumeBatchedInput(mChoreographer.getFrameTimeNanos()); } } void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if (mInputEventReceiver != null) { if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) { scheduleConsumeBatchedInput(); } } doProcessInputEvents(); } }
After the Input time is processed, it will eventually be passed to the dispatchTouchEvent of DecorView, which is the familiar Input event distribution
Animation callback call stack
In general, we often call view Callback will be used during postonanimation_ ANIMATION
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); } }
So when is the view called back As for postonanimation, I intercepted a picture. You can have a look by yourself. The most exposed operations should be startScroll and Fling
According to the content of its post, the following is the fling animation after the desktop slides and lets go.
In addition, our Choreographer's FrameCallback also uses CALLBACK_ANIMATION
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); }
Traversal call stack
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //To improve the priority, first post syncbarrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } final class TraversalRunnable implements Runnable { @Override public void run() { // Really start to execute measure, layout and draw doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // SyncBarrier remove mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // Real start performTraversals(); } } private void performTraversals() { // measure operation if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } // layout operation if (didLayout) { performLayout(lp, mWidth, mHeight); } // draw operation if (!cancelDraw && !newSurface) { performDraw(); } }
TraceView example for doTraversal
9. Vsync request of the next frame
Due to the existence of animation, sliding and flying operations, we need a continuous and stable frame rate output mechanism. This involves the request logic of Vsync. In the case of continuous operations, such as animation, sliding and flying, the next Vsync application will be triggered when the doFrame of each frame is applied according to the situation, so that we can obtain the continuous Vsync signal.
Look at the following scheduleTraversals call stack (Vsync request will be triggered in scheduleTraversals)
Both invalidate and requestLayout, which we are familiar with, trigger Vsync signal requests
Let's take Animation as an example to see how Animation drives the next Vsync to continuously update the picture
##10. ObjectAnimator animation driven logic
android/animation/ObjectAnimator.java
public void start() { super.start(); }
android/animation/ValueAnimator.java
private void start(boolean playBackwards) { ...... addAnimationCallback(0); // Add Animation Callback when the animation start s ...... } private void addAnimationCallback(long delay) { ...... getAnimationHandler().addAnimationFrameCallback(this, delay); }
android/animation/AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { if (mAnimationCallbacks.size() == 0) { // post FrameCallback getProvider().postFrameCallback(mFrameCallback); } ...... } // The mFrameCallback callback here calls back to the doFrame, which post s itself private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); if (mAnimationCallbacks.size() > 0) { // post yourself getProvider().postFrameCallback(this); } } };
Calling postFrameCallback will go to mchoreographer postFrameCallback, where the Vsync request logic of Choreographer will be triggered
android/animation/AnimationHandler.java
public void postFrameCallback(Choreographer.FrameCallback callback) { mChoreographer.postFrameCallback(callback); }
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); } } }
Through the above animation Start setting, using choreographer Framecallback interface. Every frame requests the next Vsync
TraceView example of one frame during animation
11. Summary of source code
- Choreographer # is a thread singleton and must be bound to a Looper, because there is a Handler inside it that needs to be bound to the Looper, which is generally bound to the Looper of the App main thread
- DisplayEventReceiver is an abstract class whose JNI code part will create a Vsync listener object of IDisplayEventConnection. In this way, the Vsync interrupt signal from AppEventThread can be passed to the Choreographer object. When the Vsync signal arrives, the onVsync function of DisplayEventReceiver will be called.
- DisplayEventReceiver also has a scheduleVsync function. When the application needs to draw the UI, it will first apply for a Vsync interrupt, and then draw in the onVsync function of interrupt processing.
- Choreographer , defines a , FrameCallback , interface. Whenever Vsync arrives, its doFrame function will be called. This interface is very helpful for the implementation of Android Animation. I used to control the time myself, but now I finally have a fixed time interrupt.
- The main function of Choreographer is to call the callback function set by the user through postCallback when receiving the Vsync signal. At present, there are five types of callbacks defined:
- 1 . CALLBACK_INPUT: handle input events
- 2 . CALLBACK_ANIMATION: Processing animation
- 3 . CALLBACK_INSETS_ANIMATION: handle callbacks related to Insets Animation
- 4 . CALLBACK_TRAVERSAL: it deals with the drawing of controls such as UI
- 5 . CALLBACK_COMMIT: handle the callback related to commit
- The Item initialization (obtain\setup) of ListView # will be in input and animation, depending on
- CALLBACK_INPUT ,CALLBACK_ANIMATION # will modify the properties of view, so it is necessary to contact callback first_ Traversal execution
#4, APM and Choreographer
Due to the location of Choreographer, many performance monitoring methods are done by Choreographer. In addition to the built-in frame drop calculation, the FrameCallback and FrameInfo provided by Choreographer expose the interface to the App, so that App developers can monitor the performance of their App through these methods. The common methods are as follows:
- doFrame callback using FrameCallback
- Monitoring with FrameInfo
- Use: adb shell dumpsys gfxinfo# framestats
- Example: ADB shell dumpsys gfxinfo.com meizu. flyme. launcher framestats
- Monitoring with SurfaceFlinger
- Using: adb shell dumpsys SurfaceFlinger – latency
- Example: adb shell dumpsys SurfaceFlinger – latency com meizu. flyme. launcher/com. meizu. flyme. launcher. Launcher#0
- Use SurfaceFlinger PageFlip mechanism for monitoring
- Usage: adb service call SurfaceFlinger 1013
- Note: system permission is required
- Choreographer's own frame drop calculation logic
- BlockCanary performance monitoring based on Looper
1. Use the doFrame callback of FrameCallback
FrameCallback interface
public interface FrameCallback { public void doFrame(long frameTimeNanos); }
Interface use
Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
Interface processing
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { ...... postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); }
TinyDancer uses this method to calculate FPS( https://github.com/friendlyrobotnyc/TinyDancer)
##2. Monitoring with FrameInfo
adb shell dumpsys gfxinfo 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,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration, 0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000, 0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000, 0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000, 0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,
##3. Monitoring with SurfaceFlinger
Command interpretation:
- The unit of data is nanosecond, and the time is based on the startup time
- Each command will get 128 lines of frame related data
Data:
- The first row of data indicates the refresh interval refresh_period
- Column 1: the data in this part represents the time point when the application draws the image
- Column 2: the vertical synchronization time before SF (software) submits the frame to h / w (hardware) for drawing, that is, the timestamp submitted to hardware after drawing each frame. This column is the timestamp of vertical synchronization
- 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 completes the drawing.
Drop frame jank calculation
Each line can get a value through the following formula. This value is a standard. We call it jankflag. If the jankflag of the current line changes from the jankflag of the previous line, it will be called the frame
ceil((C - A) / refresh-period)
4. Use the SurfaceFlinger PageFlip mechanism 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();
##5. Choreographer's own frame drop calculation logic
SKIPPED_ FRAME_ WARNING_ The default limit is 30, which is determined by debug choreographer. Skipwarning is controlled by this attribute
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."); } }
##6. BlockCanary
Blockcanary computing uses Looper's Message mechanism for performance monitoring. It records the front and back of each Message in the MessageQueue to achieve the purpose of monitoring performance
android/os/Looper.java
public static void loop() { ... 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); } ... } }
5, MessageQueue and Choreographer
In fact, the so-called asynchronous Message is like this. We can insert a Barrier into the Message queue through enqueueBarrier, and then the synchronous messages in the queue whose execution time is later than the Barrier will be blocked by the Barrier and cannot be executed until we call removeBarrier to remove the Barrier, while the asynchronous Message will not be affected, Messages are synchronous messages by default. Unless we call setAsynchronous of Message, this method is hidden. Only when initializing the Handler can you specify that the messages sent to the Handler are asynchronous through parameters. In this way, setAsynchronous of Message will be called in the enqueueMessage of the Handler to set the Message to be asynchronous, from the above Handler enqueueMessage can be seen in the code.
In fact, the so-called asynchronous message has only one function, that is, it can still be processed normally without the influence of the Barrier when setting the Barrier. If the Barrier is not set, the asynchronous message is no different from the synchronous message. You can remove the Barrier through removeSyncBarrier
##1. An example of syncbarrier used in Choreographer
postSyncBarrier when scheduling Traversals
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //To improve the priority, first post syncbarrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } }
removeSyncBarrier in doTraversal
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // SyncBarrier remove mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // Real start performTraversals(); } }
When the Choreographer Posts messages, these messages will be set as Asynchronous, so the priority of these messages in the Choreographer will be higher,
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);
6, Vendor optimization
System manufacturers can directly modify the source code and make use of this convenience to do some functions and optimization. However, due to the problem of confidentiality, the code will not be put up directly. I can give you a general idea, and those who are interested can be discussed in private
1. Mobile event optimization
There is no input message in Choreographer itself, but after modifying the source code, the input message can be directly sent to Choreographer. With these input messages, Choreographer can do some things, such as respond in advance without waiting for Vsync
2. Background animation optimization
When an Android App retreats to the background, as long as he is not killed, don't be surprised what he does, because this is Android. Some apps continue to call the Animation Callback in Choreographer after retreating to the background, and the execution of this Callback is completely meaningless, and the user does not know it yet, but the cpu consumption is relatively high.
Therefore, the Choreographer will optimize this situation and prohibit unqualified apps from continuing useless operations in the background
##3. Frame rendering optimization
Like mobile event optimization, with the information of Input event, in some scenarios, we can notify SurfaceFlinger not to wait for Vsync to perform synthesis directly
4. application start optimization
As we said earlier, all operations of the main thread are given to messages. If an operation and unimportant messages are arranged behind the queue, it will have an impact on the operation; By rearranging the MessageQueue, important startup messages related to startup are placed in front of the queue when the application starts, so as to speed up the startup
5. High frame rate optimization
On 90 fps mobile phones, the Vsync interval has changed from 16.6ms to 11.1ms, which brings huge performance and power consumption challenges. How to complete the necessary operations of rendering in one frame is what mobile phone manufacturers must think about and optimize:
- Performance and optimization of super App
- Game high frame rate cooperation
- Logic for switching between 90 fps and 60 fps
Original link: https://androidperformance.com/2019/10/22/Android-Choreographer/
So far, this article has ended. Reprint the articles on the Internet. Xiaobian feels very excellent. You are welcome to click to read the original text and support the original author. If there is infringement, please contact Xiaobian to delete it. Your suggestions and corrections are welcome. At the same time, we look forward to your attention. Thank you for reading. Thank you!