保持好奇心,保持激情,不被环境所同化
这是大学毕业的时候,一位挚友、学长送我的一句话。转眼即将毕业两年,很庆幸自己能够一直践行着这句话,伴随着的是满满的收获与成长。更让我庆幸的是,工作中遇到了非常多经验丰富、实力雄厚的前辈同事,得益于他们的指点、教诲,自己满足好奇心的能力也在逐步提高。
这篇文章主要分享我是如何满足自己对于Android渲染的好奇心。
Android对于View的绘制主要会经过measure、layout、draw几个步骤,那么就从measure入手,看看Android是如何调用到View的onMeasure方法的。
以上是我在一个Android自定义View的onMeasure方法中挂的断点,可以看到是通过MainThread的Lopper发起,MainThread的Handler处理Callback,然后调用到Choreographer,执行一系列的doFrame -> doCallbacks -> doTraversal -> performTraversals -> measureHierarchy。大概了解流程之后,接下来深入Choreographer、ViewRootImpl内部了解。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
...
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
...
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
以上是Choreographer第一步doFrame调用位置的关键代码,可以看到FrameDisplayEventReceiver实现了Runnable接口,Runnale的run方法中会调用doFrame方法,那么接下来需要确认这个Runnable是如何放入Lopper的消息队列的?不难发现,就在FrameDisplayEventReceiver的onVsync方法中,Handler通过sendMessage传入了msg,msg的obtain方法中传入了此Runnable。
通过在onVsync中挂断点,可以发现是通过DisplayEventReciever的dispatchVsync调用的,同时也发现FrameDisplayEventReceiver也是继承自DisplayEventReciever,然后实现了onVsync方法
/**
* Provides a low-level mechanism for an application to receive display events
* such as vertical sync.
*
* The display event receive is NOT thread safe. Moreover, its methods must only
* be called on the Looper thread to which it is attached.
*
* @hide
*/
public abstract class DisplayEventReceiver {
...
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
MessageQueue messageQueue, int vsyncSource);
private static native void nativeDispose(long receiverPtr);
@FastNative
private static native void nativeScheduleVsync(long receiverPtr);
...
/**
* Called when a vertical sync pulse is received.
* The recipient should render a frame and then call {@link #scheduleVsync}
* to schedule the next vertical sync pulse.
*
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
* @param builtInDisplayId The surface flinger built-in display id such as
* {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
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 {
nativeScheduleVsync(mReceiverPtr);
}
}
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
}
以上是DisplayEventReceiver的关键代码,可以看到主要是接收Vsync信号,Vsync(即V-Sync垂直同步),就是一个信号源。假设你的屏幕是60FPS的话,那意味着每隔1s,屏幕就有60次中断信号产生,即每隔16.666ms,就会有一次中断信号产生,也就是我们经常听说的Android帧率绘制。关于Vsync是如何产生?如何从Hardware传到SurfaceFlinger?如何从SurfaceFlinger传到这个位置?这篇文章不多做探索,对此好奇的朋友可以自行满足自己。这里我比较好奇的是,Vsync信号一直产生,Android是如何做到在有需要的时候监听Vsync信号并处理绘制渲染变化呢?
以上是我触摸手机屏幕并滑动列表的时候在DisplayEventReceiver中的scheduleVsync方法中挂的断点,可以看到整个调用栈是InputEventReceiver.dispatchBatchedInputEventPending -> ViewRootImpl.scheduleConsumeBatchedInput -> Choreographer.postCallback -> ... -> Choreographer.scheduleVsyncLocked -> DisplayEventReceiver.scheduleVsync
/**
* Posts a callback to run on the next frame.
* ...
*/
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
/**
* Posts a callback to run on the next frame after the specified delay.
* ...
*/
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
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);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
...
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
...
}
}
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
以上是Choreographer中的相关代码,当有input等操作导致Application界面需要更新的时候,Android Framework会通过Choreographer postCallback通知DisplayEventReceiver要开始监听处理Vsync信号了,那么下一个Vsync信号到来时就会调用FrameDisplayEventReceiver的onVsync方法,至此,就成功与之前分析到的doFrame流程接通了。
关于Android渲染,以上探索了Android Framework是如何触发开始渲染的。前段时间结合Android系统源码,系统阅读了罗升阳老师相关系列文章,主要讲到MainThread如何构建DisplayList,RenderThread如何将DisplayList转化为SurfaceFlinger可以接收的FrameBuffer,以及相关线程如何协作、同步等,推荐给各位
后续我会继续深入探索研究这一部分,欢迎感兴趣的朋友一起探讨。也希望自己满足好奇心的能力能够进一步增强,并且在其他能力方面逐渐有所突破!