前面有学习了Android绘制的三大流程:merge、layout、draw,但是一直都没有搞清楚绘制到显示的整体流程。借此机会,记录下自己学习过程。
我们都知道,一次完整的录制时都是从ViewRootImpl的scheduleTraversals()开始,即使调用invalidate()也是如此。
scheduleTraversals
// ViewRootImpl.java
void scheduleTraversals() {
// mTraversalScheduled阻止了多次调用
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 加入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 加入一个callback到mChoreographer
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
可以看到scheduleTraversals()主要做了两件事:
- 往当前线程的Loop加入同步屏障
- 封装了一个mTraversalRunnable加入到mChoreographer
这里就引申出来三个问题:
- mTraversalRunnable是什么?
- mChoreographer是什么?
- 同步屏障是什么?有什么用?
mTraversableRunnable
其实TraversableRunnable分装着就是measure、layout、draw三大流程。
// ViewRootImpl
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
private void performTraversals() {
final View host = mView;
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
可见,TraversalRunnable就是一个Runnable,里面做了两件事情:
- 移除同步屏障
- 开始真正的绘制流程。
总结而言就是一个图:
那上面我们知道,Choreographer加入了TraversalRunnable,那什么时候去执行TraversalRunnable呢?
Choreographer
Choreographer 翻译为编舞者,负责从显示系统接收脉冲信号,编排渲染下一帧的绘制工作,负责获取Vsync同步信号并控制UI线程完成图像绘制的类。
怎么理解这一句话呢?
一次完整的绘制,是需要CPU、GPU和显示设备的配合,但是三者是一个并行运作的状态,那怎么相互协调呢?所以引进了VSync信号机制:每16ms,硬件(或者软件)会发出一个VSync信号,CPU接收到这个信号,开始了一次绘制流程。再下一次VSync信号到来之时,Display就可以直接显示第一帧,CPU也开始绘制第二帧。就这样循环下去。
也就是说CPU和GPU必须要在这一次的VSync信号发生和下一次VSync信号到来之前要准备好这一帧的数据,否则就发生了掉帧的现象了。
可以看下图,大致可以了解VSync信号机制:
Choreographer怎么跟Vsync信号机制相挂钩呢?
Choreographer负责订阅Vsync信号和收到Vsycn信号时候,负责去开始一次绘制,具体我们可以看下源码:
我们从上面的ViewRootImpl将mTraversalRunnable封装加入mChoreographer中入手:
// ViewRootImpl
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// Choreographer
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;
// 加入mCallbackQueues
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
// 这里的delayMills为0,所以会走到scheduleFrameLocked()
if (dueTime <= now) {
scheduleFrameLocked(now);
}
...
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
...
// 判断当前thread是否运行loop,这个后续会讲到
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
}
}
}
private void scheduleVsyncLocked() {
// 注册订阅了vsync信号
mDisplayEventReceiver.scheduleVsync();
}
// DisplayEventReceiver
public void scheduleVsync() {
...
// 注册订阅了vsync信号
nativeScheduleVsync(mReceiverPtr);
}
private static native void nativeScheduleVsync(long receiverPtr);
可以看到主要是两个动作:
- mCallbackQueues加入了Runnable
- 从jni层注册了Vsync信号
那什么时候会收到Vsync信号回来呢?
在DisplayEventReceiver的disjpatchVsync()方法可以看到,该方法是jni层调用的
// DisplayEventReceiver,由jni调用
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
//Choreographer内部类DisplayEventReceiver,重写了onVsync方法
@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);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
这里可以看到,其实mHandler就是当前主线程的handler,当接收到onVsync信号的时候,将自己封装到Message中,等到Looper处理,最后Looper处理消息的时候就会调用run方法,这里是Handler的机制,不做解释。
在run方法中,调用了doFrame()方法:
// Choreographer
void doFrame(long frameTimeNanos, int frame) {
...
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
// 从mCallbackQueues取出
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
}
// CallbackRecord
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
// 这里也即是调用了TraservalRunnable的run方法,也即是三个绘制流程
((Runnable)action).run();
}
}
可见当vsync信号来临的时候,主要做了两件事情
- 从mCallbackQueues取出callback
- 执行callback,这里最后会执行到TraservalRunnable的三大绘制流程
这里是不是说,Choreographer是不是只要注册一次以后,都可以收到Vsync信号呢?
其实不是的,Vsync信号需要每次都去注册,而且只能接收到一次。这样能保证在不需要重新绘制的情况下,Choreographer也就不需要接收信号,就不会去注册Vsync信号。
Choreographer在哪里初始化的?
以上介绍了Choreographer是如何发挥作用的,但是Choregrapher是在哪里初始化的?
// Choreographer
public static Choreographer getInstance() {
return sThreadInstance.get();
}
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
由上面的代码可以看到:Choreographer存在于Looper的线程的ThreadLocal中,每个looper的线程可能都有一个Choreographer。
而且在ViewRootImpl中可以看到他的身影,这部分不继续介绍了。
同步屏障
上面出现了三个东西关于同步屏障:
1. 加入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
2. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
3. 设置成异步消息
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
同步屏障:
顾名思义就是在阻止同步消息的运行,而异步消息可以直接运行。而我们日常使用的Message都是同步消息。
这得从Handler的机制说起,在MessageQueue中有一个消息链表,类似下图,我们的Message会根据执行时间排序,在next()方法依次取出下一个需要执行的Message,如果加入了同步屏障,则会滤过同步消息,直接取异步消息执行,直到屏障被移除掉。
** 为什么要这么做呢?**
原因是确保异步消息尽可能快的被执行,在这里是确保mTraversalRunnable优先被执行。
Surface 简单了解
以上只是介绍了绘制流程怎么跟VSync信号相结合,但是并没有说到绘制流程怎么显示到显示器中,这个涉及到Surface的概念,这部分我只是了解了一部分。
surface 存在于ViewRootImpl中
// ViewRootImpl
public final Surface mSurface = new Surface();
// Surface
public Surface() {
}
但是,Surface 还没真正的初始化,这里并没有这么简单,他是连接显示系统的关键,真正的初始化在WMS中,在Acitivity中有讲到窗口机制,在WMS创建对应的Surface,再映射到ViewRootImpl的Surface中。ViewRoot对Surface绘制,WMS对Surface初始化及操作。
而在ViewRootImpl中draw方法中会调用drawSoftware,内部实际上是对Surface的canvas的绘制
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
canvas = mSurface.lockCanvas(dirty);
...
mView.draw(canvas);
...
surface.unlockCanvasAndPost(canvas);
}
以上大致是,ViewRootImpl会绘制操作到Surface,而Surface是跟WMS共享的,
SurfaceFinger 简单了解
SurfaceFlinger是运行在一个独立的进程,由init进程启动。
WMS在创建Surface的时候会通过SurfaceSession中间会话层去请求SurfaceFlinger去创建的对应的Layer
SurfaceFlinger 会维持Layer的Z轴序列,并对Layer最终计算输出
对于SurfaceFlinger太复杂了,后面需要去更多的了解。
Choreographer 做帧率检测
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
checkFramesInternal(frameTimeNanos);
Choreographer.getInstance().postFrameCallback(this);
}
});
参考
http://gityuan.com/2017/02/25/choreographer/
https://www.jianshu.com/p/75139692b8e6
https://blog.csdn.net/windskier/article/details/7041610
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6256.html