赞
踩
先来看一个高频面试题:
介绍一下 Android 屏幕显示原理,开发编写的 View 控件,是怎么变成屏幕上显示的图像的?
这个问题该怎么回答呢?
一个思路是先整体串讲,宏观的把Android UI 显示原理的关键知识点都涉及到,然后再细化具体介绍,知识点如下:
本文针对 UI 绘制请求与绘制时机,简单的分析一下 UI 刷新机制,并不涉及 vSync 信号的生成等底层内容。
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);
...
}
}
其中 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(); } }
通过 mTraversalScheduled 变量可以看出,不是每次调用 requestLayout、invalidate 方法就会触发一次 UI 重绘的,而是要等 mTraversalRunnable 被执行后才会接收下一次的重绘请求。
在 mTraversalRunnable 中调用了 performTraversals() 进行真正的 UI 绘制,而 UI 真正绘制的时机则取决于 mChoreographer 触发回调的时机。
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); } } }
首先将 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);
}
}
如注释所示,scheduleFrameLocked 中需要切换到指定线程中调用 scheduleVsyncLocked:
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
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); }
当要绘制的图像时间戳晚于一个帧刷新周期时,会去进一步计算异常跳过的帧数,如果跳过的帧数过大,就可以看到非常眼熟的一条日志了:"Skipped xx frames! The application may be doing too much work on its main thread"
随后通过 doCallbacks 回调触发执行 UI 绘制,也就是执行 ViewRootImpl 传过来的 TraversalRunnable、调用 performTraversals 方法,由顶而下的执行界面绘制逻辑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。