[Source code analysis] Android View drawing process--from the synchronization barrier

The drawing process of View is mainly included in the ViewRootImpl#performTraversal method. This method mainly includes the three steps of measure, layout and draw, which will not be described here. We mainly focus on how the Framework initiates the call each time it is drawn.

1. requestLayout request drawing

Whether the app side calls the requestLayout or the invalidate method to trigger the redraw, it will eventually call schedueTraversalthe method to start the drawing process, but it is only in the process to decide whether to execute measure, layout or draw.

ViewRootImpl.java

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}


void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

Let's focus on what the scheduleTraversals method does

void scheduleTraversals() {
    //mTraversalScheduled字段保证同时间内的多次修改只会执行一次渲染过程(如更新text时)
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 代码1.1 
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 代码1.2
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

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

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();


void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        //执行绘制过程 包括:measure、layout、draw过程
        performTraversals();
      
    }
}

The mTraversalRunnable here is a thread that calls the doTraversal method, and the doTraversal method mainly calls performTraversals to draw the view.

2. Place synchronization barriers through Handler

First, the current messageQueue will be obtained through the handler, and the postSyncBarrier method will be called.

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

2.1 What is a synchronization barrier

The syncBarrier here is the synchronization barrier . The name is very bluffing. In fact, it is a normal message with a target of null. The target is because it needs to distribute the message to the corresponding target, and the synchronization barrier does not need to be distributed.

In MessageQueue, when we were learning Handler before, we should remember to detect the target when sending a message through the handler. If the target of the current msg is found to be null, an exception will be thrown

When passing sendMessage normally, the calling process is as follows:

sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage

//将当前msg入队 会检测msg是否满足条件
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    synchronized (this) {
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
  ...
}

正常通过handler发送流程如上,而放置同步屏障是直接在postSyncBarrier方法内将当前target为null的msg放置在队列头部。表明当前系统需要开始进行绘制流程。

2.2 同步屏障作用

同步屏障的作用是保证当Vsync信号到来的时候,当前进程能够第一时间执行渲染流程, 而不是再去执行别的线程抛给handler的同步任务。在MessageQueue的next方法中可以体现

synchronized (this) {
    // Try to retrieve the next message.  Return if found.
    final long now = SystemClock.uptimeMillis();
    Message prevMsg = null;
    Message msg = mMessages;
    if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
            prevMsg = msg;
            msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
    }

上述的if判断中,就是如果当前队列头部msg的target为null,那么就说明该msg就是放置的一个同步屏障,后面的do-while循环就会找到第一个异步的msg,也就是包含绘制信息的msg。

2.3 向编舞者抛一个用于绘制的callback

在上述代码1.2的地方又向编舞者mChoreographer发送了一个callback,传递的是当前用来绘制的线程mTraversalRunnable,他会在下一帧到来时候进行回调, 即在下一个 VSync 信号到来时会执行TraversalRunnable-->doTraversal()-->performTraversals()-->绘制流程

Choreographer,即编舞者,控制整个系统内的渲染绘制的操作,协调动画、输入以及绘制的时机,每个主线程(Looper)都会有自己的编舞者,他是线程单例的(基于ThreadLocal实现)...

Choreographer.java

public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}

...


private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    ...

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        //根据回调方法的类型,将callback放置在不同的队列内
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
       
        if (dueTime <= now) {
            scheduleFrameLocked(now);  //正常绘制是进入这里,开始请求下一次Vsync信号
        } else {
        //如果是想在后面某个时间绘制,会进入这里
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}


//scheduleFrameLocked 会继续调用scheduleVsyncLocked();方法
//mDisplayEventReceiver是FrameDisplayEventReceiver类型
//它继承了FrameDisplayEventReceiver
private void scheduleVsyncLocked() {
    mIsVsyncScheduled = true;
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
        mDisplayEventReceiver.scheduleVsync();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
//DisplayEventReceiver.java
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 {
    //这里会JNI调用到native层,这里具体后续再写一篇文章总结下
        nativeScheduleVsync(mReceiverPtr);
    }
}

请求Vsync信号的过程这里不再阐述了,反正就是会在native层请求Vsync信号(requestNextVsync)

2.4 收到Vsync信号后的调用

当底层进行分发信号的时候,native层会去调用到DisplayEventReceiver的dispatchVsync方法


private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
        long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
    onVsync(timestampNanos, physicalDisplayId, frame,
            new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
}
//然后进入FrameDisplayEventReceiver重写的onVsync方法
//FrameDisplayEventReceiver.java 不仅继承了DisplayEventReceiver,
//同时它本身还是个runnable

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
        ....
        
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {
    try {
      
          ...
          
        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;
        ScrollOptimizer.setVsyncTime(mTimestampNanos);
        mLastVsyncEventData = vsyncEventData;
        //将自身封装成msg
        Message msg = Message.obtain(mHandler, this);
        //将消息设置为异步消息
        msg.setAsynchronous(true);
        //通过handler发送 代码2.1
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
        

   public void run() {
       mHavePendingVsync = false;
       doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
    }
        
}

可以看到代码2.1处FrameDisplayEventReceiver向handler发送了一个异步消息,由于之前设置了同步屏障,在取消息的时候会默认找到第一个异步消息也就是当前msg,执行其run方法

我们来看下doFrame方法


void doFrame(long frameTimeNanos, int frame,
        DisplayEventReceiver.VsyncEventData vsyncEventData) {
    final long startNanos;
    final long frameIntervalNanos = vsyncEventData.frameInterval;
    try {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                    "Choreographer#doFrame " + vsyncEventData.id);
        }
        synchronized (mLock) {
            mIsVsyncScheduled = false;
            if (!mFrameScheduled) {
                traceMessage("Frame not scheduled");
                return; // no work to do
            }

            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                mDebugPrintNextFrameTimeDelta = false;
                Log.d(TAG, "Frame time delta: "
                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
            }

            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            //是否当前执行的时候已经超过一帧的时间, 即使存在同步屏障,如果当前有正在执行的任务
            //就有可能导致doFrame被延迟执行
            if (jitterNanos >= frameIntervalNanos) {
                final long skippedFrames = jitterNanos / frameIntervalNanos;
                //掉帧数如果超过30帧就打印log
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % frameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                            + "which is more than the frame interval of "
                            + (frameIntervalNanos * 0.000001f) + " ms!  "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                if (DEBUG_JANK) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                traceMessage("Frame time goes backward");
                scheduleVsyncLocked(); //当前掉帧了,继续请求vsync信号
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    traceMessage("Frame skipped due to FPSDivisor");
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
                    vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
            mLastFrameIntervalNanos = frameIntervalNanos;
            mLastVsyncEventData = vsyncEventData;
        }

        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
       

        ScrollOptimizer.setUITaskStatus(true);
        mFrameInfo.markInputHandlingStart();
        //开始处理不同的callback方法
        //输入事件的回调(最先执行)
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

        mFrameInfo.markAnimationsStart();
        //普通动画的回调
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        //Insets动画的回调
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                frameIntervalNanos);

        mFrameInfo.markPerformTraversalsStart();
        //这个是用来绘制的callback也就是ViewRootImpl刚才post的callback
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
        //用来处理当前帧绘制完后的一些操作 运行在traversal之后
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        ScrollOptimizer.setUITaskStatus(false);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   
        if (getMainThreadInstance() == Choreographer.this) {
            TurboSchedMonitor.getInstance().releaseAppToken();
        }
    }
    ...
    
    mIsDoFrameProcessing = false;
}

继续看doCallback方法

void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        // We use "now" to determine when callbacks become due because it's possible
        // for earlier processing phases in a frame to post callbacks that should run
        // in a following phase, such as an input event that causes an animation to start.
        final long now = System.nanoTime();
        //从对应callback队列内取出到达执行时间的callback ,封装成CallbackRecord
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;

       ...
       
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        //遍历所有的callback
        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));
            }
            //执行callback的run方法,也就是可以执行mTraversalRunnable的run方法来绘制了
            c.run(frameTimeNanos);
           
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

到这里,一个完整的绘制线程的调用就结束了,然后就开始可以执行真正的绘制操作了。

这里就开始执行ViewRootImpl内的mTraversalRunnable线程,在该线程内的doTraversal方法内,会首先移除掉先前放置的同步屏障,保证后续handler的同步任务能够正常执行


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

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
      //真正绘制流程
        performTraversals();

        ...
    }
}

3 同步屏障可能导致的问题

同步屏障没用好会导致很严重的问题,如果某个子线程主动通过Choreographer来进行更新UI,但是由于某种原因导致放置的同步屏障未能remove掉,就会导致后续的所有同步任务都无法执行。

之前就接触过一例问题,就是由于系统主线程的同步屏障未能正常被remove,从而导致了所有的系统功能(依赖于handler的同步任务)都无法正常调用,排查了好久才定位到是由于同步屏障的原因。

因此,还是需要加深对同步屏障的理解。

下面用一张图来总结一下 Android绘制流程是如何借助同步屏障以及Choreographer 来完成绘制流程:

总结

  1. 同步屏障SyncBarrier的作用是保证当Vsync信号到来时,能够第一时间执行Choreographer向handler发送的异步消息,但是这不能保证当前的绘制任务一定能够在一帧内结束(可能是前一个同步任务事件较长或者当前的绘制任务耗时较长...)
  2. 当View请求刷新页面时,不会马上开始,他需要先请求Vsync信号,等待Vsync信号到来时才会开始进行计算屏幕数据、layout、measure以及draw等流程;这些流程结束后,新页面也不会立马显示到屏幕上。需要等下一个Vsync信号到来时通过buffer缓存交换才能显示到屏幕上
  3. 如果当前屏幕没有任何改变的话,底层也会进行每16.6ms进行一次页面的刷新过程(通过Vsync信号来实现,但是app不会接收Vsync事件);也就是说,界面不变时屏幕也会保持每16.6ms的刷新频率,但是CPU/GPU不会执行绘制流程
  4. App侧可以通过postFrameCallback方法来监听界面的帧率,判断当前的丢帧情况

Ps: 本文参考了:juejin.cn/post/689420…

Guess you like

Origin juejin.im/post/7116808036135469093