Reading the source code long knowledge | Android lag is really due to "drop frame"?

Author: Tang Zaixuan

Andriod interface freezes because of frame dropping, and frame dropping is because the speed of producing frames cannot keep up with the speed of consuming frames.

The frame rate of consumption is linked to the screen refresh rate. The screen is like a comic strip. If 60 frames per second are played, the rate of consuming one frame is 1000/60 = 16.6 ms, that is, the screen will fetch the next frame of display content every 16.6 ms If you don’t get it, you can only continue to display the last frame, and the picture will freeze. This is called "dropped frame". It sounds like something that cannot be retrieved is lost. In fact, it describes "the display content missed once "Display opportunity" describes a behavior of the screen hardware.

Why does the screen not get the display content? You have to find the reason from the software layer. With this question, read the Framework source code.

Choreographer

ViewRootImpl is the root view of all views in an Activity, and the traversal of the View tree is initiated by it:

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    // 编舞者
    Choreographer mChoreographer;
    // 触发遍历 View 树
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 向 Choreographer 抛 View树遍历任务
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    // 遍历 View 树任务
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    // 构建遍历 View 树任务实例
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
}

ViewRootImpl triggers the View tree traversal by throwing tasks to Choreographer.

Choreographer is a class under the android.view package. It literally means "choreographer", which implies "need to synchronize the two". Choreographer is to synchronize the movement with the rhythm. The Choreographer synchronizes the "drawing content" with the "vertical sync signal".

Store drawing tasks

public final class Choreographer {
    // 输入任务
    public static final int CALLBACK_INPUT = 0;
    // 动画任务
    public static final int CALLBACK_ANIMATION = 1;
    // view树遍历任务
    public static final int CALLBACK_TRAVERSAL = 2;
    // COMMIT任务
    public static final int CALLBACK_COMMIT = 3;
    // 暂存任务的链式数组
    private final CallbackQueue[] mCallbackQueues;
    // 主线程消息处理器
    private final FrameHandler mHandler;

    // 抛绘制任务
    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) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

    // 抛绘制任务的具体实现
    private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            // 1\. 将绘制任务根据类型暂存在链式结构中
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            // 2\. 订阅下一个垂直同步信号
            if (dueTime <= now) {
                // 立刻订阅下一个垂直同步信号
                scheduleFrameLocked(now);
            } else {
                // 在未来的某个时间点订阅垂直同步信号
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

    // 主线程消息处理器
    private final class FrameHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                ...
                case MSG_DO_SCHEDULE_CALLBACK:
                    // 在未来时间点订阅垂直同步信号
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

    void doScheduleCallback(int callbackType) {
        synchronized (mLock) {
            if (!mFrameScheduled) {
                final long now = SystemClock.uptimeMillis();
                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                    // 订阅下一个垂直同步信号
                    scheduleFrameLocked(now);
                }
            }
        }
    }
}

After Choreographer receives a new drawing task, it will perform two actions:

  1. Drawing task into the chain :
public final class Choreographer {
    // 绘制任务链
    private final class CallbackQueue {
        // 任务链头结点
        private CallbackRecord mHead;

        // 绘制任务入链(按时间升序)
        public void addCallbackLocked(long dueTime, Object action, Object token) {
            CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
            CallbackRecord entry = mHead;
            if (entry == null) {
                mHead = callback;
                return;
            }
            // 头插入
            if (dueTime < entry.dueTime) {
                callback.next = entry;
                mHead = callback;
                return;
            }

            //中间插入或尾插入
            while (entry.next != null) {
                if (dueTime < entry.next.dueTime) {
                    callback.next = entry.next;
                    break;
                }
                entry = entry.next;
            }
            entry.next = callback;
        }
    }

    // 绘制任务结点
    private static final class CallbackRecord {
        // 下一个绘制任务
        public CallbackRecord next;
        // 绘制任务应该在这个时刻被执行
        public long dueTime;
        // 描述绘制任务的代码段
        public Object action;   
        ...
        // 执行绘制任务
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }
}

Choreographer receives four task types, namely input, animation, View tree traversal, and COMMIT. Each task is abstracted into CallbackRecord, and similar tasks form a task chain CallbackQueue in chronological order. The four task chains are stored in the mCallbackQueues[] array structure.

  1. Subscribe to the next vertical sync signal
public final class Choreographer {
    private void scheduleFrameLocked(long now) {
        // 若已经订阅过下个垂直同步信号,则什么也不做
        if (!mFrameScheduled) {
            // 当下一个垂直同步信号到来时,需要执行绘制任务
            mFrameScheduled = true;
            if (USE_VSYNC) {
                // 不管走哪个分支,最终都会调用scheduleVsyncLocked()来注册接收垂直同步信号
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                ...
            }
        }
    }

    // 委托 DisplayEventReceiver 来注册垂直同步信号
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

    // 绘制一帧
    void doFrame(long frameTimeNanos, int frame) {
        synchronized (mLock) {
            // 若不需要响应这个垂直同步信号,则直接返回,该帧不绘制任何东西
            if (!mFrameScheduled) {
                return; 
            }
            ...
        }
        ...
    }

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
        // 垂直同步信号回调
        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int 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 abstract class DisplayEventReceiver {
    // 注册接收下一个垂直同步信号
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "...");
        } else {
            // 向SurfaceFlinger订阅下一个垂直同步信号
            nativeScheduleVsync(mReceiverPtr);
        }
    }
    ...
}

Not every vertical synchronization signal will be subscribed to and processed by the upper layer. Only when Choreographer subscribes to the next vertical synchronization signal, SurfaceFlinger will call back the signal to the upper layer through onVsync().

Before the arrival of the first vertical signal in the figure, the upper layer called postCallback() to throw a drawing task to Choreographer, and at the same time subscribed to the next signal and set mFrameScheduled to true, indicating that the next frame needs to be drawn. When the first signal is generated, onVsync() is called back, and doFrame() is thrown to the main thread for execution. After the execution is completed, mFrameScheduled is set to false. Because there is no subsequent subscription action, the upper layer will not receive subsequent onVsync( ) Callback, and nothing new will be drawn.

Perform drawing tasks

After ViewRootImpl throws the drawing task to Choreographer, the task is not executed immediately, but is temporarily stored in the drawing task chain and registered to receive the next vertical synchronization signal. Only when the next signal passes the onVsync() callback, it will be executed:

public final class Choreographer {
    // 垂直同步信号接收器
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { 
            ...
            // 发送异步消息到主线程,执行当前的Runnable,即doFrame()
            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);
        }
    }
}

Whenever the vertical synchronization signal is called back, a frame drawing task doFrame() to be executed will be pushed to the main thread.

public final class Choreographer {
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            // 如果没有订阅这一帧的垂直同步信号,则直接退出不绘制
            if (!mFrameScheduled) {
                return; // no work to do
            }
        ...
        try {
            // 处理这一帧的输入事件
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            // 处理这一帧的动画
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            // 处理这一帧的 View 树遍历
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            // 所有绘制任务结束后执行 COMMIT 任务
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            ...
        }
        ...
    }
}

When drawing each frame, tasks will be processed in the order of "input event", "animation", "View tree traversal", and "COMMIT".

The processing function doCallback() is defined as follows:

public final class Choreographer {
    // 暂存绘制任务的链式数组
    private final CallbackQueue[] mCallbackQueues;

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            // 从指定类型的任务链中根据当前时间点取出要执行的任务
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            ...
        }
        try {
            // 执行任务链上被取出的所有任务
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
            }
        } finally {
            ...
        }
    }

    // 任务实体类
    private static final class CallbackRecord {
        public CallbackRecord next;
        public Object action;
        ...
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                // 执行任务
                ((Runnable)action).run();
            }
        }
    }
}

Following this, the "View Tree Traversal" task pushed by ViewRootImpl was finally executed.

Among them, extractDueCallbacksLocked() is a method of the task chain CallbackQueue, which is used to obtain all tasks that need to be executed at the current time point:

private final class CallbackQueue {
    // 任务链头结点
    private CallbackRecord mHead;

    // 获取当前时间节点之前的所有任务,这些任务都需要在当前帧被执行
    public CallbackRecord extractDueCallbacksLocked(long now) {
        CallbackRecord callbacks = mHead;
        if (callbacks == null || callbacks.dueTime > now) {
            return null;
        }

        CallbackRecord last = callbacks;
        CallbackRecord next = last.next;
        // 遍历任务链,以现在时间点为分割线,从链上摘取过去的任务
        while (next != null) {
            if (next.dueTime > now) {
                last.next = null;
                break;
            }
            last = next;
            next = next.next;
        }
        mHead = next;
        // 以链的形式返回所有过去任务
        return callbacks;
    }
}

When drawing the current frame, the current time will be used as the dividing line to extract all previous tasks from the task chain and execute them one by one in chronological order.

Calculate the above code on paper:

The above figure shows that before the first vertical synchronization signal has arrived, the upper layer has added 3 tasks to the task chain. The execution time of the first two is before the arrival of the first signal, and the third task is in the first signal. Execute after a signal.

The subscription to the first signal is completed when the first task enters the chain, and the third task is executed after the first signal, so its subscription behavior doScheduleCallback() is first stored in the main thread message queue. After the first signal comes, subscribe to the second signal.

When the first vertical synchronization signal arrives, doFrame() is thrown to the main thread. It extracts tasks 1 and 2 before the current time node from the task chain and executes them. When the task is executed, the next message "subscribe to the next vertical synchronization signal for task 3" is taken from the main thread message queue and executed. After all these are completed, the main thread can only be in a daze, waiting for the next vertical synchronization signal.

When the second vertical synchronization signal comes, the remaining tasks 3 in the task chain are taken out and thrown to the main thread for execution. The task chain is empty. When task 3 is executed, the main thread has nothing to do and can only wait until the upper layer to throw tasks to Choreographer.

The cause of the freeze is frame dropping?

put off

In the above situation, the drawing task can be completed within one frame interval. What if the task is time-consuming and the execution time exceeds the frame interval?

After the arrival of the first vertical signal, tasks 1 and 2 were thrown to the main thread for execution. This time their execution time was slightly longer, exceeding one frame interval, resulting in the delay of subscribing to the next signal function. For the upper layer, the second onVsync() callback was missed, which means that tasks 1 and 2 missed a display timing, and task 3 missed a rendering timing. For the bottom layer, the display will fetch the display content from the graphics buffer when it sends out the vertical synchronization signal. This time the content is not received and can only continue to display the previous image.

After tasks 1 and 2 are executed, the main thread will "subscribe to the next signal". When the third signal comes, the display gets the rendering results of tasks 1 and 2 from the graphics buffer, and task 3 is also thrown to the main thread for execution. .

In this case, all drawing tasks in the task chain have been postponed.

merge

What if there are other time-consuming tasks in the main thread besides drawing tasks?

Suppose that before the arrival of the first vertical synchronization signal, the upper layer has thrown 3 drawing tasks into the task chain, and their execution time is set between the first, second, and third vertical synchronization signals.

When the first vertical synchronization signal arrived, doFrame() was thrown to the main thread message queue, but the main thread was occupied by an I/O task, so doFrame() never got the chance to execute. And the function that subscribes to subsequent signals cannot be executed, so the second and third onVsync() will not be called until the I/O operation is completed.

What is the difference in doFrame() that is delayed?

public final class Choreographer {
    private Choreographer(Looper looper, int vsyncSource) {
        ...
        // 帧间隔 = 1 秒 / 刷新率
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    }

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
        private long mTimestampNanos;

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            ...
            // 记录垂直信号到达的时刻
            mTimestampNanos = timestampNanos;
            ...
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            // 垂直信号到达时刻会和doFrame()一起被抛到主线程
            doFrame(mTimestampNanos, mFrame);
        }
    }

    void doFrame(long frameTimeNanos, int frame) {
        synchronized (mLock) {
            ...
            // 当前帧实际被绘制的时刻
            startNanos = System.nanoTime();
            // 计算当前帧绘制延迟 = 当前时刻 - 本应绘制的时刻
            final long jitterNanos = startNanos - frameTimeNanos;
            // 如果延迟大于帧间隔
            if (jitterNanos >= mFrameIntervalNanos) {
                // 计算跳过的帧数,如果帧数超过阈值则log警告
                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.");
                }
                // 计算帧延迟相对于帧间隔的偏移量
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                // 修正帧绘制时刻,将其对齐到最新到达的垂直同步信号
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            ...
            // 将上一帧绘制时刻更新为最新垂直同步信号时刻
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            // 渲染当前帧(传入的frameTimeNanos是最新的垂直同步信号时刻)
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            ...
        }
        ...
    }
}

doFrame() calculates the frame delay before drawing the frame, that is, the difference between the current time and the time the frame should be drawn.

If the frame delay is greater than the frame interval, the drawing time frameTimeNanos will be corrected and aligned to a vertical synchronization signal time before the current time. Then this moment is passed to doCallbacks() as a basis for picking up tasks from the task chain, and all previous tasks will be taken off.

At this point, tasks 1, 2, and 3 will all be taken off, because they are all past tasks relative to the frameTimeNanos that follow, so the execution of doFrame() this time will combine the original three doFrame() drawing tasks. Execution, if the drawing task is executed fast enough, there is a chance to subscribe to it before the next vertical synchronization signal arrives, so that the drawing content of tasks 1, 2, and 3 will be displayed in the next frame, as shown in the following figure:

When the main thread is occupied by time-consuming operations, frame drop occurs, that is, frames that should be rendered and displayed will miss the opportunity to display. But for the upper layer, nothing is lost, but the drawing tasks that should be performed at different frame intervals are merged together for display.

What happens if the three drawing tasks are time-consuming?

At this time, certain conditions in doCallbacks() will be triggered:

public final class Choreographer {
    void doFrame(long frameTimeNanos, int frame) {
        synchronized (mLock) {
            ...
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            // 如果延迟大于帧间隔
            if (jitterNanos >= mFrameIntervalNanos) {
                ...
                // 计算帧延迟相对于帧间隔的偏移量
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                // 修正帧绘制时刻,将其对齐到最新的垂直同步信号
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            ...
            // 将上一帧绘制时刻更新为最新垂直同步信号时刻
            mLastFrameTimeNanos = frameTimeNanos;

            // 如果该帧相对于上一帧来说是旧帧,  跳过当前帧并订阅下一个垂直同步信号
            if (frameTimeNanos < mLastFrameTimeNanos) {
                scheduleVsyncLocked();
                return;
            }
        }

        try {
            // 渲染当前帧
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            // 在输入,动画,View树遍历任务都执行完毕之后,执行 COMMIT 任务
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            ...
        }
        ...
    }

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            ...
            // 如果是 COMMIT 任务
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                // 计算帧绘制延迟
                final long jitterNanos = now - frameTimeNanos;
                // 如果帧绘制延迟 >= 2倍帧间隔
                if (jitterNanos >= 2 * mFrameIntervalNanos) {
                    // 计算帧偏移量并追加一个帧间隔的时长
                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos;
                    // 将mLastFrameTimeNanos对其到当前时刻前的第二个垂直同步信号时刻
                    frameTimeNanos = now - lastFrameOffset;
                    mLastFrameTimeNanos = frameTimeNanos;
                }
            }
        }
        ...
    }
}

Because the execution time of doFrame() exceeds 2 frame intervals, when the input, animation, and View tree traversal tasks are executed, and the COMMIT task is executed, the frame time is triggered, and mLastFrameTimeNanos is shifted forward for a period of time. The second vertical synchronization signal moment before the end of the drawing task. The purpose of this is to prevent more dropped frames due to this timeout drawing. Because doFrame() checks the drawing time of the current frame and the previous frame before drawing the current frame:

// 如果该帧相对于上一帧来说是旧帧, 跳过当前帧并订阅下一个垂直同步信号
if (frameTimeNanos < mLastFrameTimeNanos) {
    scheduleVsyncLocked();
    return;
}

If when the last doFrame() is almost completed, the upper layer throws several drawing tasks to Choreographer. The drawing time frameTimeNanos of these drawing tasks must be less than the last frame drawing end time mLastFrameTimeNanos. If the COMMIT task is not executed, mLastFrameTimeNanos will be moved forward. , The new drawing task will miss an execution opportunity.

Here I also share an Android learning PDF + architecture video + source notes , advanced architecture technology advanced brain map, Android development interview special materials, advanced advanced architecture materials collected and organized by several big guys .

If you need it, you can click to get it !

If you like this article, you might as well give me a small like, leave a message in the comment area, or forward and support it~

Guess you like

Origin blog.csdn.net/ajsliu1233/article/details/108240689