[Análisis del código fuente] Proceso de dibujo de Android View: desde la barrera de sincronización

El proceso de dibujo de View se incluye principalmente en el método ViewRootImpl#performTraversal, que incluye principalmente tres pasos de medición, diseño y dibujo, que no se describirán aquí. Nos centramos principalmente en cómo Framework inicia la llamada cada vez que se dibuja.

1. dibujo de solicitud de diseño de solicitud

Ya sea que el lado de la aplicación llame al método requestLayout o invalidate para activar el redibujado, eventualmente llamará schedueTraversalal método para iniciar el proceso de dibujo, pero solo en el proceso se decide si ejecutar la medida, el diseño o el dibujo.

ViewRootImpl.java

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


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

Centrémonos en lo que hace el método 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();
      
    }
}

El mTraversalRunnable aquí es un hilo que llama al método doTraversal, y el método doTraversal principalmente llama a performTraversals para dibujar la vista.

2. Colocar barreras de sincronización a través de Handler

Primero, la cola de mensajes actual se obtendrá a través del controlador y se llamará al método 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 ¿Qué es una barrera de sincronización?

El syncBarrier aquí es la barrera de sincronización . El nombre es muy engañoso. De hecho, es un mensaje normal con un objetivo nulo. El objetivo es porque necesita distribuir el mensaje al objetivo correspondiente, y la barrera de sincronización no necesita para ser distribuido

En MessageQueue, cuando estábamos aprendiendo Handler antes, debemos recordar detectar el objetivo al enviar un mensaje a través del controlador. Si se encuentra que el objetivo del mensaje actual es nulo, se lanzará una excepción.

Al pasar sendMessage normalmente, el proceso de llamada es el siguiente:

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…

Supongo que te gusta

Origin juejin.im/post/7116808036135469093
Recomendado
Clasificación