当前位置:   article > 正文

Android-UI-绘制请求与绘制时机_android界面绘制时机

android界面绘制时机

先来看一个高频面试题:

介绍一下 Android 屏幕显示原理,开发编写的 View 控件,是怎么变成屏幕上显示的图像的?

这个问题该怎么回答呢?

一个思路是先整体串讲,宏观的把Android UI 显示原理的关键知识点都涉及到,然后再细化具体介绍,知识点如下:

  • Activity 显示原理(Window/DecorView/ViewRoot)
  • UI 刷新机制(Choreographer/vSync)
  • UI 绘制原理(Measure/Layout/Draw)
  • Surface 原理(Surface/SurfaceFlinger)

本文针对 UI 绘制请求与绘制时机,简单的分析一下 UI 刷新机制,并不涉及 vSync 信号的生成等底层内容。

接收 UI 重绘请求的 ViewRootImpl

ViewRootImpl 中的 performTraversals 中会依次调用 performMeasure、performLayout、performDraw,分别对应于 measure、layout、draw,由顶而下的进行界面绘制逻辑。

调用 View 控件 requestLayout、invalidate 等方法请求 UI 重绘时,会统一调用到 ViewRootImpl 的 scheduleTraversals 方法,代码如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ...
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中 postSyncBarrier 插入一个消息屏障 block 普通消息,以保证主线程可以优先来执行接下来的绘制工作。mTraversalRunnable 的实现如下:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

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

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        performTraversals();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

通过 mTraversalScheduled 变量可以看出,不是每次调用 requestLayout、invalidate 方法就会触发一次 UI 重绘的,而是要等 mTraversalRunnable 被执行后才会接收下一次的重绘请求。

在 mTraversalRunnable 中调用了 performTraversals() 进行真正的 UI 绘制,而 UI 真正绘制的时机则取决于 mChoreographer 触发回调的时机。

触发 UI 绘制的 Choreographer

ViewRootImpl 接收 UI 重绘请求后,将真正的 UI 绘制时机交给了 Choreographer,而 Choreographer 会在每次 vSync 信号到来时执行 UI 绘制。

调用 Choreographer 的 postCallback 方法将 UI 绘制 TraversalRunnable 传入后,会进一步调用 Choreographer 的 postCallbackDelayedInternal 方法,代码如下:

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

首先将 UI 绘制 action 记录到 mCallbackQueues 队列中,然后根据处理时间决定立即调用 scheduleFrameLocked ,或发送异步消息延时调用 scheduleFrameLocked。

scheduleFrameLocked 方法关键代码如下:

private void scheduleFrameLocked(long now) {
    // 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.
    if (isRunningOnLooperThreadLocked()) {
        scheduleVsyncLocked();
    } else {
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtFrontOfQueue(msg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如注释所示,scheduleFrameLocked 中需要切换到指定线程中调用 scheduleVsyncLocked:

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}
  • 1
  • 2
  • 3

scheduleVsync 表示要接受下一次 vSync 信号,等到 vSync 信号到来时会由 SurfaceFlinger 回调通知。直接来看 Choreographer 接受到 vSync 信号后的处理,关键代码如下:

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            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.");
            }
        }
    }
    ...
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当要绘制的图像时间戳晚于一个帧刷新周期时,会去进一步计算异常跳过的帧数,如果跳过的帧数过大,就可以看到非常眼熟的一条日志了:"Skipped xx frames! The application may be doing too much work on its main thread"

随后通过 doCallbacks 回调触发执行 UI 绘制,也就是执行 ViewRootImpl 传过来的 TraversalRunnable、调用 performTraversals 方法,由顶而下的执行界面绘制逻辑。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/501821
推荐阅读
相关标签
  

闽ICP备14008679号