Android 图形架构 之七——Choreographer 源码分析
Posted 薛瑄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 图形架构 之七——Choreographer 源码分析相关的知识,希望对你有一定的参考价值。
前言
Android 图形架构 之一 ——概述
Android 图形架构 之二—— SurfaceFlinger 启动和连接
Android 图形架构 之三—— 创建Layer、Surface、SurfaceControl
Android 图形架构 之四——图形缓冲区的申请和消费流程及核心类
Android 图形架构 之五——深入分析addView所发生的的一切
Android 图形架构 之六——深入分析draw()是如何工作的
Android 图形架构 之七——Choreographer 源码分析
android图形架构 之八——硬件VSync、VSync-app、Vsync-sf
在Android4.1之后增加了Choreographer机制,用于同Vsync机制配合,控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。其实UI显示的时候每一帧要完成的事情只有这三种。如下图是官网的相关说明:
Choreographer接收显示系统的时间脉冲(垂直同步信号-VSync信号),在下一个frame渲染时控制执行这些操作。
Choreographer中文翻译过来是"舞蹈指挥",字面上的意思就是优雅地指挥以上三个UI操作一起跳一支舞。这个词可以概括这个类的工作,如果android系统是一场芭蕾舞,他就是Android UI显示这出精彩舞剧的编舞,指挥台上的演员们相互合作,精彩演出。
通过Choreographer#postFrameCallback设置callback,在下一个frame被渲染时,会执行这个callback,是在Choreographer 中的Looper 执行的。例如:View的绘制流程,就是通过postFrameCallback设置回调,然后被执行的。
Callback有4种类型,Input、Animation、Draw,还有一种是用来解决动画启动问题的。这四种操作都是这么触发的。
收到VSync信号后,顺序执行3个操作,然后等待下一个信号,再次顺序执行3个操作。假设在第二个信号到来之前,所有的操作都执行完成了,即Draw操作完成了,界面显示第一帧的内容。那么第二个信号来到时,开始更新界面。每帧的时间不应超过 16ms,以达到每秒 60 帧的呈现速度(为什么是 60fps?)视觉上就不会感觉卡顿。
如果您的应用存在界面呈现缓慢的问题,系统会不得不跳过一些帧,这会导致用户感觉您的应用不流畅。我们将这种情况称为卡顿。
关于 帧 的概念,就像是动画的每一格图像,每一格就像是一张图片,快速连续的播放,因为视觉的停留,看上去就是动态的。 而在Android系统中,这里的帧 是 指的是两个VSync信号之间的这段时间,而不是具体待显示的内容,跳帧的意思就是 例如 UI 操作A需要在16ms 内完成,但是它运行了35ms,那么下一个UI操作B,只能在35ms 后执行,本来UI操作B 要在16ms 开始执行的,我们认为它跳了一帧。(注意 UI操作B 执行的时候,Choreographer 会对齐VSync时钟,也就是说认为他是在32ms 开始执行的)
第二个信号到来时,Draw操作没有按时完成,导致第三个时钟周期内显示的还是第一帧的内容。
一、调用mChoreographer.postCallback
大家应该都熟悉界面的绘制流程了,下面看下ViewRootImpl的requestLayout
@Override
public void requestLayout()
if (!mHandlingLayoutInLayoutRequest)
checkThread();//检查是否在主线程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
scheduleTraversals();
void scheduleTraversals()
if (!mTraversalScheduled) //同一帧内不会多次调用遍历
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//拦截同步Message
//Choreographer回调,执行绘制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
二、Choreographer启动
public ViewRootImpl(Context context, Display display)
...
//获取Choreographer实例
mChoreographer = Choreographer.getInstance();
...
public static Choreographer getInstance()
return sThreadInstance.get();
//一个线程对应一个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!");
return new Choreographer(looper);
;
三、构造函数
private Choreographer(Looper looper, int vsyncSource)
//一个线程对应一个Looper,一个Choreographer
mLooper = looper;
//初始化FrameHandler。接收处理消息。满足条件的VSync信号的具体处理
mHandler = new FrameHandler(looper);
//初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,一般为60HZ。
//变量USE_VSYNC 表示是否用了Vsync同步机制
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
//每一帧的时间
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//回调队列,例如 postFrameCallback 传入的callback,在处理下一阵的时候,回调它
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++)
mCallbackQueues[i] = new CallbackQueue();
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
CallbackQueue 队列的四种类型
public static final int CALLBACK_INPUT = 0; //输入
public static final int CALLBACK_ANIMATION = 1; //动画
public static final int CALLBACK_TRAVERSAL = 2; //视图绘制
public static final int CALLBACK_COMMIT = 3; //提交 ( 这一类型是在API level=23的时候添加的)
CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。其中每种类型都是使用单向链表来组织,和Message 中的消息组织方式、创建、回收都是一样的
四、流程分析
先来大概看一下,整体流程,有个整体轮廓,具体分析每一个函数时,不会迷路
图片都是从网上找的,最后会给出原文链接
4.1、 FrameHandler
先来看看FrameHandler,因为下面很多发送消息的,都会到这里处理
private final class FrameHandler extends Handler
public FrameHandler(Looper looper)
super(looper);
@Override
public void handleMessage(Message msg)
switch (msg.what)
case MSG_DO_FRAME:
//收到同步信号后的处理函数,也就是帧处理函数
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//请求垂直同步信号
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//处理 Callback
doScheduleCallback(msg.arg1);
break;
4.2、postCallbackDelayedInternal
postCallback 和 postCallbackDelayed 最终都会调用到下面的postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis)
synchronized (mLock)
//获取当前时间
final long now = SystemClock.uptimeMillis();
//计算出截止时间
final long dueTime = now + delayMillis;
//action 就是回调接口,按照截止时间加入指定类型的队列中,队列截止时间顺序是由小到大
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now)
//当前时间已经超过截止时间了,直接执行
scheduleFrameLocked(now);
else
//当前时间未超过截止时间,发送msg,最终在FrameHandler 进行处理
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
4.3、scheduleFrameLocked
加锁执行Frame,这个锁是在调用scheduleFrameLocked的时候,外部添加的synchronized
private void scheduleFrameLocked(long now)
if (!mFrameScheduled)
mFrameScheduled = true;
if (USE_VSYNC)
// 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
//通过handler 发送MSG_DO_SCHEDULE_VSYNC消息,最终也是调用scheduleVsyncLocked来处理的
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
else
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
//通过handler 发送MSG_DO_FRAME消息,最终调用doFrame来处理
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
//请求垂直同步信号
private void scheduleVsyncLocked()
mDisplayEventReceiver.scheduleVsync();
4.4、DisplayEventReceiver#dispatchVsync
系统是通过调用dispatchVsync,发送VSync信号的。接着调用 onVsync来处理。Choreographer里的有个类FrameDisplayEventReceiver,它继承了DisplayEventReceiver,并覆写了onVsync
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame)
onVsync(timestampNanos, builtInDisplayId, frame);
4.5、FrameDisplayEventReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource)
super(looper, vsyncSource);
@Override
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;
//这里callback 传入this,就是下面的run 函数
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);
4.6、doFrame
void doFrame(long frameTimeNanos, int frame)
final long startNanos;
synchronized (mLock)
//开始处理帧任务前(在scheduleFrameLocked中),设为true,doFrame 处理完后,设为false,表示一帧处理完后,才会接受下一帧的处理
if (!mFrameScheduled)
return; // no work to do
//发送帧的时间
long intendedFrameTimeNanos = frameTimeNanos;
//获取当前时间
startNanos = System.nanoTime();
//计算出时间差,例如帧很早就发送了,但是过一会才得到处理
final long jitterNanos = startNanos - frameTimeNanos;
//时间差 大于帧的周期
if (jitterNanos >= mFrameIntervalNanos)
//计算跳过了几帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//下面这段log ,应该很熟悉了
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT)
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
//计算最后一帧的偏移
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK)
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
//帧对齐,与最后一帧的开始对齐
frameTimeNanos = startNanos - lastFrameOffset;
//为什么会出现时间回溯,我还没搞清楚
if (frameTimeNanos < mLastFrameTimeNanos)
if (DEBUG_JANK)
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
//请求下一次VSync信号
scheduleVsyncLocked();
return;
if (mFPSDivisor > 1)
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0)
scheduleVsyncLocked();
return;
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
//false 表示当前没有正在处理的帧
mFrameScheduled = false;
//记录上一次frame开始时间,修正后的
mLastFrameTimeNanos = frameTimeNanos;
try
//在doCallbacks 函数中,执行postCallbackDelayedInternal传入的回调接口
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);
关于上面帧对齐,下面来看两种情况:
- 没有跳帧
当前frame启动时间直接设置为当前VSync信号时间。
虽然Frame 1执行时间超过一个VSync周期,但是因为没有跳帧(没有大于两个周期),frame 2的执行时间还是设置为frameTimeNanos3
- 发生跳帧
修正当前frame的启动时间到最近的VSync信号时间。
frame 1 执行时间超过2个时钟周期,frame 2 延后执行时间大于一个时钟周期,系统认为这时候影响较大,判定为跳帧了,将frame 2的时间修正为frameTimeNanos4,比VSync真正到来的时间晚了一个时钟周期。
无论当前frame是否跳帧,修正完时间后,frame的处理时间与VSync信号的发送时间还是在一个节奏上的,可能延后了N个周期
4.7、doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos)
CallbackRecord callbacks;
synchronized (mLock)
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
//extractDueCallbacksLocked 函数就是按照 当前的时间,把队列划分为两部分。
//因为队列是按照到期时间由小到大 排列的,以当前时间为界,分为两部分。返回小的部分队列的头,大的部分队列的头赋值给mHead
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null)
return;
mCallbacksRunning = true;
//提交帧时,如有必要,更新帧时间。
//仅在到达提交阶段晚于2帧时才更新帧时间。
//这确保了回调所观察到的帧时间将始终从一帧增加到下一帧,并且永远不会重复。
//我们永远都不希望下一帧的开始帧时间小于或等于前一帧的提交帧时间。
//请记住,下一帧很可能已经安排好了,因此我们可以确保提交时间始终至少落后一帧,以确保安全
//CALLBACK_COMMIT 是为了解决,动画绘制时间过长导致的动画跳帧的问题
if (callbackType == Choreographer.CALLBACK_COMMIT)
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounterAndroid 图形架构 之二—— SurfaceFlinger 启动和连接
Android 图形架构 之二—— SurfaceFlinger 启动和连接
《大型网站技术架构》读书笔记之七:随需应变之网站的可扩展架构