I’ve had more reads and likes than I could have imagined. Thanks for bringing me the second article in this series this week.

The story begins

Interviewer: Do you have any problems in development? How do you usually deal with it? Guy: Uh… I have never encountered the problem of lag, I usually write high quality code, there is no lag. Interviewer:…

The above dialogue seems to be a joke, but some time ago, I really met a guy who came for an interview. He asked if he had ever encountered the problem of caton, how to deal with it? He said no, he said his code didn’t get stuck. This may not seem like a bad answer, but I would consider you to have zero experience in the area of catton optimization.

Caton this topic, I believe most students with two years or more of work experience should be able to tell the general. A common response might be something like this:

The screen is refreshed every 16 milliseconds, which is 60 times per second. The human eye can perceive the frame rate of the lag at 24 frames per second. The solution to this problem is to place time-consuming operations on subthreads, keep the View hierarchy to a minimum, and use include, ViewStub tags, etc., so that you can draw more than 24 frames per second.

If you go a little deeper, what is the underlying principle of Catton? How to understand 16 ms refresh once? Will the View draw every 16 milliseconds if the interface does not update?

This question I believe will be baffled by a person, including most of the students of more than 3 years of experience, if not to read the source code, may not be able to answer this question. Of course, I hope you’re in the minority

Next will analyze the screen refresh mechanism from the perspective of source code, in-depth understanding of the principle of caten, as well as several ways to introduce caten monitoring, hope to help you.


I. Screen refresh mechanism

Start with View#requestLayout, since this method is actively requesting UI updates, it’s perfectly fine to analyze from here.

1. View#requestLayout

protected ViewParent mParent; . public void requestLayout() { ... if (mParent ! = null & & ! mParent.isLayoutRequested()) { mParent.requestLayout(); / / 1}}Copy the code

Look mainly at comment 1, where mparent-requestLayout () will eventually call the requestLayout method of ViewRootImpl. So you might ask, why ViewRootImpl? The root View is a DecorView, and the parent of the DecorView is ViewRootImpl. Call view.assignParent(this) in the setView method of ViewRootImpl. We can assume that this is the case for the moment, and we will analyze it in detail later when we tidy up the drawing process of the View.

2. ViewRootImpl#requestLayout

public void requestLayout() { if (! MHandlingLayoutInLayoutRequest) {/ / 1 test thread checkThread (); mLayoutRequested = true; //2 scheduleTraversals(); }}Copy the code

Note 1 checks whether the thread is currently in the main thread

2.1 ViewRootImpl# checkThread

    void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
Copy the code

So this is a familiar exception, we’re talking about a child thread that can’t update the UI, throwing an exception, and that’s where we see it, viewrootime thread #checkThread

Let’s move on to note 2

2.2 ViewRootImpl# scheduleTraversals

Void scheduleTraversals() {void scheduleTraversals(); mTraversalScheduled) { mTraversalScheduled = true; MTraversalBarrier = mhandle.getLooper ().getQueue().postSyncBarrier(); / / 3. Submit a task to Choreographer mChoreographer. PostCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); if (! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } / / drawing before send a notification notifyRendererOfFramePending (); PokeDrawLockIfNeeded (); pokeDrawLockIfNeeded(); }}Copy the code

The three main points in the comments:

Note 1: Prevent requestLayout from being drawn multiple times in a short period of time. There is no point in calling requestLayout again if the frame has not been drawn before.

Note 2: Insert a synchronization barrier message into a message queue, and asynchronous messages will be processed in preference to synchronized messages in the queue. It makes sense that uI-related actions have the highest priority. For example, if you have a message queue with a lot of unfinished tasks, you should start an Activity first and then process other messages. The synchronization barrier is handled in MessageQueue’s next method:

Message next() {...for(;;) {... synchronized (this) { // Try to retrieve the next message. Returnif found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if(msg ! = null && MSG. Target == null) {// iF MSG was not empty and target was empty // passer. Find the next asynchronous messagein the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while(msg ! = null && ! msg.isAsynchronous()); }... }Copy the code

The logic is: if MSG is not empty and target is empty, it is a synchronization barrier message, and we enter the do while loop and iterate through the list until we find an asynchronous message, MSG. IsAsynchronous () is not taken out of the loop and handed over to Handler to process the asynchronous message.

Back to the above comment 3: mChoreographer. PostCallback (Choreographer. CALLBACK_TRAVERSAL mTraversalRunnable, null); “Submit a task mTraversalRunnable to Choreographer. This task will not be executed immediately

3. Choreographer

Look at the mChoreographer postCallback

3.1 Choreographer# postCallback

public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { if (action == null) { throw new IllegalArgumentException("action must not be null"); } if (callbackType < 0 || callbackType > CALLBACK_LAST) { throw new IllegalArgumentException("callbackType is invalid");  } postCallbackDelayedInternal(callbackType, action, token, delayMillis); } 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); // 2. If (dueTime <= now) {scheduleFrameLocked(now); } else {//3. When will there be a delay, draw timeout, wait for the next vsync? Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

Into the ginseng callbackType travels here is Choreographer. CALLBACK_TRAVERSAL, later said that eventually calls postCallbackDelayedInternal methods.

Note 1: Adding a task to the queue will not be executed immediately, but will be used later. ScheduleFrameLocked, normally delayMillis is 0, go here, see below analysis. Note 3: When will there be a delay, there is a call to the TextView, not for now.

3.2. Choreographer# scheduleFrameLocked

// Enable/disable vsync for animations and drawing. System property parameter, the default true private static final Boolean USE_VSYNC = SystemProperties. GetBoolean (" debug. Choreographer. Vsync ", true); . Private void scheduleFrameLocked(long now) {// avoid unnecessary if (! mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } // If running on the Looper thread, then schedule the vsync immediately, // Otherwise post a message to schedule the vsync from the UI thread // as soon as possible. //1 Perform scheduleFrameLocked directly, or through the Handler to deal with the if (isRunningOnLooperThreadLocked ()) {scheduleVsyncLocked (); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); }}}Copy the code

This method has a system argument judgment, which defaults to true, and we analyze the true case. Note 1: If the current thread is a UI thread, scheduleVsyncLocked will be executed. Otherwise, an asynchronous message will be sent to the message queue via Handler, and will eventually be processed by the main thread, so scheduleVsyncLocked will be executed directly.

3.3 Choreographer# scheduleVsyncLocked

private final FrameDisplayEventReceiver mDisplayEventReceiver;

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}
Copy the code

Call the scheduleVsync method of DisplayEventReceiver

4. DisplayEventReceiver

4.1 DisplayEventReceiver# scheduleVsync

/** * Schedules a single vertical sync pulse to be delivered when the next * display frame begins. */ public void scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { nativeScheduleVsync(mReceiverPtr); //1, call from native code}} // Called from native code. //2, call from native code @unused () private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }Copy the code

The logic here is: through JNI, say to the underlying layer, let me know when the next vsync pulse is coming. And then when the next vsync signal comes, you get the underlying JNI callback, which is dispatchVsync and the method is called, and then the empty onVsync method is called, and the implementation class does some of the work itself.

/**
     * Called when a vertical sync pulse is received.
     * The recipient should render a frame and then call {@link #scheduleVsync}
     * to schedule the next vertical sync pulse.
     *
     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
     * timebase.
     * @param builtInDisplayId The surface flinger built-in display id such as
     * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
     * @param frame The frame number.  Increases by one for each vertical sync interval.
     */
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    }
Copy the code

This is the focus of the screen refresh mechanism. The application must request a Vsync signal from the underlying layer, and the next time a Vsync signal arrives, the application is notified via JNI, and then the application draws logic.

Look back, DisplayEventReceiver implementation class is the inner class FrameDisplayEventReceiver Choreographer, code is not much, direct post

5. Choreographer

5.1 $FrameDisplayEventReceiver Choreographer

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; public FrameDisplayEventReceiver(Looper looper) { super(looper); } @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { // 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(); // Correct the timestamp, 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; Message msg = Message.obtain(mHandler, this); //1 callback is this, and the run method MSG. SetAsynchronous (true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); / / 2}}Copy the code

According to 4.1 above, onVsync is called after receiving the vsync signal. What does it do? The Handler is used to insert an asynchronous message into the message queue and specify the time to execute it. In this case, the callback is passed to the run method, which calls doFrame(mTimestampNanos, mFrame). If the Handler has a time-consuming operation at this time, Looper will round to the next message and the run method will be called only after the time-consuming operation is completed. Then doFrame(mTimestampNanos, mFrame) will be called. What did doFrame do? What happens if the call is slow? Continue to look at

5.2 Choreographer# doFrame

void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { ... long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); // final Long jitterNanos = startNanos-frametimenanos; // Final Long jitterNanos = startNanos-frametimenanos; If (jitterNanos >= mFrameIntervalNanos) {// count how many frames have been skipped, Final Long skippedFrames = jitterNanos/mFrameIntervalNanos; // SKIPPED_FRAME_WARNING_LIMIT is set to 30 by default. 2. To bounce or bounce lightly over. To bounce or bounce lightly over. 2. To bounce or bounce lightly over. " + "The application may be doing too much work on its main thread."); } // Count how long it has been since the last frame. Each frame is 16 milliseconds, so the lastFrameOffset is between 0 and 15 milliseconds. Final Long lastFrameOffset = jitterNanos % mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Missed vsync by "+ (jitterNanos * 0.000001f) +" ms "+ "which is more than the 8frame interval of" + (mFrameIntervalNanos * 0.000001f) + "ms! "+ "Skipping" + "Skipping "+" frames and setting frame "+ "time to" + (frameoffset * 0.000001f) + "ms in the past."); } // Frame offset = startNanos-lastFrameoffset; // Frame offset = startNanos-lastFrameoffset; } //2, the time is backward, perhaps because the system time is changed, 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."); } // The next vsync signal is applied, and the process is the same as above. scheduleVsyncLocked(); return; } mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } Choreographer#doFrame";}} try {trace.tracebegin (trace.trace_tag_view, "Choreographer#doFrame"); AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); }}Copy the code

Analysis:

1. Calculate the time difference between receiving vsync signal and doFrame being called. The interval of vsync signal is 16 milliseconds. 2. If the time goes backwards, perhaps because the system time has been changed, do not draw, but re-register the next vsync signal 3. Normally you go into doCallbacks,callbackType The sequence is Choreographer.CALLBACK_INPUT, Choreographer.CALLBACK_ANIMATION, Choreographer.CALLBACK_TRAVERSAL, and Choreographer K_COMMIT

Look at the logic in doCallbacks

5.3 Choreographer# doCallbacks

void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); //1. When the task was added to the queue, It has said the callbacks = mCallbackQueues [callbackType] extractDueCallbacksLocked (now/TimeUtils. NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; . //2. Update the time of this frame, If (callbackType == Choreographer.CALLBACK_COMMIT) {final Long jitterNanos = nowframetimenanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Commit callback delayed by "+ (jitterNanos * 0.000001f) +" MS which is more than twice the frame interval of "+ (mFrameIntervalNanos * 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)); } // 3. Execute the task, c.run(frameTimeNanos); }}... }Copy the code

The main thing here is to fetch the corresponding type of task, and then execute the task. Note 2: If (callbackType == Choreographer.CALLBACK_COMMIT) is the last step of the process. When the data has been drawn and ready to commit, the timestamp will be corrected to ensure that the commit time is always after the last Vsync time. This text may not be easy to understand, but let me quote a picture

The doCallbacks start with the frameTimeNanos2 and are 2.2 frames past CALLBACK_COMMIT. Nowframetimenanos >= 2 * frameintervalnanos, lastFrameOffset = jitterNanos % frameIntervalnanos FrameTimeNanos = now-lastFrameoffset is just 3.

This task must be related to the drawing of the View. Remember what type requestLayout started passing through, Choreographer.CALLBACK_TRAVERSAL, So the task class that comes out of the queue is mwroalRunnable, type TraversalRunnable, defined in ViewRootImpl, let’s go back to ViewRootImpl

6. ViewRootImpl

Viewrootimp #scheduleTraversals

6.1 ViewRootImpl# scheduleTraversals

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        ...
		mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}
Copy the code

The mTraversalRunnable task goes around, requests the vsync signal, receives the vsync signal, and is finally invoked.

6.2 ViewRootImpl $TraversalRunnable

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

6.3 ViewRootImpl# doTraversal

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false; // Remove the synchronization barrier mhandler.getLooper ().getQueue().removesyncbarrier (mTraversalBarrier); performTraversals(); }}Copy the code

I’m going to remove the synchronization barrier message, and then I’m going to call performTraversals, and performTraversals is a little bit of code, so let me highlight it

6.4 ViewRootImpl# performTraversals

	private void performTraversals() {/ / mAttachInfo assigned to View host. DispatchAttachedToWindow (mAttachInfo, 0); // Execute enqueued actions on every traversalin casea detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler); . / / 1if(! MStopped | | mReportNextDraw) {/ / Ask the host how big it wants to be / / 1.1 measuring a performMeasure (childWidthMeasureSpec, childHeightMeasureSpec); / Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measureif
            // needs be

			if(lp.horizontalWeight &gt; 0.0f) {width += (int) ((mwidth-width) * lp.horizontalweight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain =true;
            }
            if(lp.verticalWeight &gt; 0.0f) {height += (int) ((mheight-height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain =true; } //1.2. If the LinearLayout has a weight set, measure it twiceif(measureAgain) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); }}... / / 2. Layoutif(didLayout) {// call the View's onLayout method, and then call the View's onLayout method performLayout(lp, mWidth, mHeight); }... / / 3. The pictureif(! cancelDraw &amp; &amp; ! newSurface) { performDraw(); }}Copy the code

As you can see, the three methods of the View call back to measure, Layout, and draw are inside the performTraversals, but the point to notice here is that the LinearLayout will measure twice when it’s weighted.

At this point, the screen refresh mechanism is analyzed, and the whole process is summarized:

7. Summary

The requestLayout of the View will be called to the requestLayout method of the ViewRootImpl and then submit a drawing task to Choreographer through the scheduleTraversals method. When the vsync signal arrives, it is called back via JNI callback, and an asynchronous task is posted to the main thread message queue via Handler. Finally, ViewRootImpl performs the drawing task. Call performTraversals, which contains callbacks to the View’s three methods.

Although the flow chart on the Internet is very beautiful, it is not as impressive as drawing one by yourself

After careful reading, presumably you should be clear about the screen refresh mechanism:

The application needs to proactively request vsync. When vsync arrives, the application is notified via JNI, and then the View’s three drawing methods are invoked. If no request is made, such as a requestLayout, the View’s draw method is not called. This View inside ViewRootImpl is actually a DecorView.

There are two places where frames will be dropped. One is that the main thread has other time-consuming operations, so doFrame does not have a chance to call within 16 milliseconds of the vsync signal being emitted, corresponding to 3 in the figure below. Another is that the current doFrame method takes too long to draw, and when the next vsync signal comes, the frame has not been drawn yet, resulting in frame loss, corresponding to 2 in the figure below. 1 is normal

This is a very good picture, so you can look at this picture and do your own research. If there is anything else I don’t know about Choreographer, I read this article well written on Choreographer analysis.

2. How to monitor application lag?

The above analysis of the screen refresh mechanism from the perspective of source code, why the main thread has time-consuming operation will lead to a lag? The principle must be well known, so how to find the usual development of the code that will cause delays?

The following is a summary of several popular and effective lag monitoring methods:

2.1 Based on message queues

2.1.1 Replace the Printer of Looper

Looper exposed a method

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
Copy the code

The loop method in Looper has this code

    public static void loop() {...for(;;) {... // This must bein a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if(logging ! = null) { logging.println(">>>>> Dispatching to " + msg.target + "" +
                        msg.callback + ":" + msg.what);
            }
Copy the code

If logging is not empty, Looper will call logging.println. We can calculate the time difference between Looper getting messages twice by setting Printer. If the time is too long, it means that Handler processing time is too long. By printing out the stack information directly, you can locate the time-consuming code. However, the println method argument involves string concatenation and is recommended only for Debug mode for performance reasons. A representative open source library based on this principle is BlockCanary. Take a look at the BlockCanary core code:

Class: LooperMonitor

    public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if(! MStartTimestamp mStartTimestamp = System.currentTimemillis (); mStartThreadTimestamp = SystemClock.currentThreadTimeMillis(); mPrintingStarted =true; startDump(); //2, start dump stack information}else{//3, final long endTime = system.currentTimemillis (); mPrintingStarted =false;
            if(isBlock(endTime)) { notifyBlockEvent(endTime); } stopDump(); }} private Boolean isBlock(long endTime) {return endTime - mStartTimestamp > mBlockThresholdMillis;
    }
    
Copy the code

The principle is that a Looper is considered to be stuck when comparing the time difference between two processing messages, such as more than 3 seconds. Details, you can study the source code, such as the message queue only one message, after a long time to the message queue, this situation should be processed, BlockCanary how to deal with?

In the Android development master class, Zhang Shaowen said that wechat internal monitoring scheme based on message queue has defects:


startDump()
startDump()
AbstractSampler
mRunnable

abstract class AbstractSampler {

    private static final int DEFAULT_SAMPLE_INTERVAL = 300;

    protected AtomicBoolean mShouldSample = new AtomicBoolean(false);
    protected long mSampleInterval;

    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            doSample(); // Set when calling startDumptrueSet when stopfalse
            if(mShouldSample.get()) { HandlerThreadFactory.getTimerThreadHandler() .postDelayed(mRunnable, mSampleInterval); }}};Copy the code

As you can see, the call to doSample followed by mRunnable via Handler is equivalent to a circular call to doSample until stopDump is called.

The doSample method has two class implementations, StackSampler and CpuSampler. The stack is analyzed by looking at StackSampler’s doSample method

protected void doSample() { StringBuilder stringBuilder = new StringBuilder(); // Get the stack informationfor(StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) { stringBuilder .append(stackTraceElement.toString()) .append(BlockInfo.SEPARATOR); } synchronized (sStackMap) {// synchronized (sStackMap)if(sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) { sStackMap.remove(sStackMap.keySet().iterator().next()); } sStackMap.put(system.currentTimemillis (), stringBuild.toString ()); }}Copy the code

So BlockCanary can call several methods in a row and figure out exactly which one is taking time, by opening a loop to get the stack information and save it to your LinkedHashMap to avoid misjudgments or missed calls. Core code on the first analysis here, other details you can go to see the source code.

2.1.2 Inserting an Empty Message into a message queue

This is the way to see it.

Insert an empty message into the header of the main thread message queue every 1 second through a monitoring thread. Assuming that the message is not consumed by the main thread after 1 second, the blocking time for the message to run is between 0 and 1 second. In other words, if we need to monitor a 3-second lag, we can determine that the main thread has a lag of more than 3 seconds if the header message is still not consumed in the fourth poll.

2.2 plug pile

The compilation process is slotted (for example, using AspectJ) to include time-monitoring code at method entrances and exits. The original method:

public void test() {doSomething();
}
Copy the code

After compiling the pile, the method looks like this

public void test(){
    long startTime = System.currentTimeMillis();
    doSomething(); long methodTime = System.currentTimeMillis() - startTime; // The calculation method takes time}Copy the code

Of course, here’s how it works, and you might actually need to encapsulate it, something like that

public void test(){
    methodStart();
    doSomething();
    methodEnd();
}

Copy the code

Add methodStart and methodEnd methods at the entrance and exit of each method to be monitored, similar to the pile insertion point.

Of course, the disadvantages of this piling method are obvious:

  • Unable to monitor system methods
  • – APK size will increase (more code for each method)

Note:

  • Filter simple method
  • You just need to monitor the methods that the main thread executes

2.3 other

As an extension: Facebook Open source Profilo

Third, summary

The article revolves around the topic of Catton

  1. From the perspective of source code analysis of the screen refresh mechanism, the bottom every 16 milliseconds to send a VSYN signal, application interface to update, must first request to the bottom vSYNC signal, so that the next 16 milliseconds vSYNC signal, the bottom will notify the application through JNI, and then through the main thread Handler to execute the View drawing task. Therefore, there are two causes of lag. One is the time-consuming operation of the main thread, which leads to the delay in the execution of the View drawing task. The other is that the View drawing takes too long, perhaps because there are too many levels, or the drawing algorithm is too complex, which leads to the failure to complete the data preparation before the next Vsync signal, resulting in frame drop lag.

  2. This section introduces several popular methods of monitoring cadons, the principle of BlockCanary, a representative based on message queues, and the way to add code to each method entry and exit to calculate the time of the method by compiling a pile.

In the interview to deal with the problem of lag, can be around the principle of lag, screen refresh mechanism, lag monitoring these aspects to answer, of course, lag monitoring this piece, also can use TraceView, SysTrace and other tools to find out the lag code. Before BlockCanary came along, TraceView and Systrace were essential analytics tools for developers. Now, I think it’s good to be able to explain BlockCanary’s principles, but for vendors who do system App development and maintenance, it’s not easy to access open source libraries. Therefore, it is necessary to understand the use of TraceView and Systrace tools.

This article mainly introduces the principle of the lag and lag monitoring, as to how the View is drawn, the difference between the software drawing and the hardware drawing, the drawing process after the completion, how to update to the screen, this involves a lot of content, there is time to sort it out.

Leave questions in the comments section, and that’s it


Choreographer How to monitor BlockCanary, an application that uses caton