[源码分析]Android View绘制流程--从同步屏障说起

3,150 阅读6分钟

View的绘制过程主要都包含在ViewRootImpl#performTraversal方法内,这个方法内主要包括了measure、layout、draw这三个步骤,具体就不放在这里讲述了。我们主要关注下,每次绘制时Framework内是怎么发起到调用的。

1. requestLayout请求绘制

app侧不论是调用requestLayout或是调用invalidate方法来触发重绘,最终都会调用schedueTraversal方法,开始绘制流程,只不过是在该流程中决定是否执行measure、layout或者draw。

ViewRootImpl.java

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


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

我们重点看下scheduleTraversals方法做了哪些工作

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();
      
    }
}

这里的mTraversalRunnable就一个包含调用doTraversal方法的线程,而doTraversal方法主要就是调用performTraversals来进行view的绘制过程

2.通过Handler放置同步屏障

首先这里会通过handler获取当前的messageQueue,并调用了postSyncBarrier方法

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 什么是同步屏障

这里的syncBarrier就是同步屏障,看着名字很唬人,其实他就是一个target为null的message 普通消息有target是因为它需要将消息分发给对应的target,而同步屏障不需要被分发。

MessageQueue内的,我们之前在学习Handler的时候,就应该记得通过handler发送message的时候会对target进行检测,如果发现当前msg的target为null,会抛异常

正常通过sendMessage的时候调用流程如下:

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…