Choreographer 概述
Android 4.1 之后增加了Choreographer 机制,用于同Vsync配合,同步处理输入,动画,绘制三个UI操作,实际上UI显示每一帧的时候要完成的操作只有这几样,下面是官网的说明:
- Choreographer接收显示系统的垂直同步信号(Vsync),在下一个Frame 渲染的时候执行这些操作
- Choreographer 中文的含义是编舞者,可以理解为知道UI操作一起顺序协调工作
通过Choreographer#postFrameCallback设置callback,再下一个Frame 被渲染的实际到来时,会执行这个callback,是
Choreographer 中的Looper 对象执行的
Callback 有四种类型 input,animation,traversal,commit 这四种操作都是 Choreographer 通过Vsync 触发的
Choreographer 解析
Choreographer 类图:
choreographer_classDiagram.jpg
View 更新流程
Choreographer 实例化
Choreographer 是单例模式,ViewRootImpl 通过 Choreographer.getInstance() 函数获取 Choreographer的实例
实现如下:
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;
}
};
通过上面的ThreadLocal,保证每一个线程对应一个Choreographer,存储的方式是以ThreadLocal作为Key,Choreographer作为value存储到ThreadLocalMap中
Choreographer 申请Vsync 流程
Choreographer 的申请Vsync流程图:
choreographer_mainflow01.jpg choreographer_mainflow02.jpg
在ViewRootImpl 中调用了 postCallback 方法后,可以看到通过 addCallBackLocked方法,添加了一条CallbackRecord 数据,其中的action 就是对应之前的ViewRootIml中的mTraversalRunnable
然后判断时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息MSG_DO_SCHEDULE_CALLBACK 到 Handler 所在的线程,Handler 的处理函数在 FrameHandler 类中,会执行
doScheduleCallback函数中的scheduleFrameLocked 方法;
scheduleFrameLocked 用于申请Vsync信号,在该函数中首先判断是否开启了Vsync;
- 如果没有开启Vsync,直接调用 doFrame方法;
- 如果开启了Vsync,判断在不在主线程中,如果在主线程中就运行scheduleVsyncLocked 函数;如果不在就切换线程,也会调用到scheduleVsyncLocked 方法,这个方法就是实际上用于申请 Vsync 的方法了
另外需要注意的是,使用choreographer中的handler 发送消息的时候,都调用了msg.setAsynchronous(true);方法;这个方法就是设置消息为异步消息,因为我们一开始的时候设置了同步屏障,所以异步消息就会先执行,这里设置为异步也就是为了让消息第一时间执行而不是受到其他Handler的影响
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
- scheduleVsyncLocked 执行流程
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
结合上面的类图,原理就是通过FrameDisplayEventReceiver 类型的mDisplayEventReceiver,请求native 层面的垂直同步信号Vsync
mDisplayEventReceiver 是 choreographer类中的成员,FrameDisplayEventReceiver 继承自 DisplayEventReceiver;设计就是为了处理Vsync 信号的申请和接收
使用 DisplayEventReceiver 的函数nativeScheduleVsync申请Vsync 信号,然后接收到Vsync信号的时候就会回调onVsync方法了
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// Ignore vsync from secondary display.
// This can be problematic because the call to scheduleVsync() is a one-shot.
// We need to ensure that we will still receive the vsync from the primary
// display which is the one we really care about. Ideally we should schedule
// vsync for a particular display.
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
+ "of its scheduled vsync.");
scheduleVsync();
return;
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
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;
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);
}
在onVsync 函数中,同样通过Handler 发送了一条消息,执行了本身的 Runnable 回调方法,也就是doFrame
Choreographer doFrame流程
- 设置当前帧的开始绘制时间,上面说过开始绘制要在vsync信号来的时候开始,保证两者时间对应;所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上
- 执行所有的Callback任务
Choreographer doCallback流程
void doFrame(long frameTimeNanos, int frame) {
......
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
按照类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法
在这个run方法中,判断了token,并且执行了action 对应的方法;
回顾一下 ViewrootImpl 中传入的方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
传入的方法就是ViewRootImpl 内定义的mTraversalRunnable 对象,所以最终又回到了ViewRootImpl 本身,通过Choreographer申请了Vsync信号,然后Choreographer接收了Vsync信号,在Traversal的流程中调用了本身的doTraversal函数
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
doTraversal 调用了 performTraversals方法,也就开始了测量,布局,绘制的步骤,同时关闭了同步屏障
网友评论