编舞者Choreographer分析(上)
在Android中Choreographer起到调度器的作用。
首先看一下构造方法
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;//处理回调的线程Looper
mHandler = new FrameHandler(looper);//处理消息的Handler
mDisplayEventReceiver = USE_VSYNC//是否使用VSYNC机制
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;//上一帧的渲染时间
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());//一帧占用用时间 1s/帧率
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));//设置fps除数,降低fps 为了低fps实验
}
在构造方法中需要传入Looper,在不同的线程中有不同的Choreographer持有其线程looper,通过threadLocal存储
private static final ThreadLocal<Choreographer> sSfThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();//获取当前线程looper
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
}
};
下面简单的追踪一下绘制页面时编舞者在其中的作用。
当ViewRootImpl调用scheduleTraversals显示页面的时候,会通过mChoreographer.postCallback方法来调用mTraversalRunnable。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
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);
}
}
}
上面的方法做了两件事。
1 将回调Runnable也就是mTraversalRunnable 以及指定的回调调用时间根据回调类型存入相应的mCallbackQueues。
2 根据制定的回调时间 如果<=now : 立即安排本帧。否则通过发送延时message的方式等到到达指定的回调时间再安排此帧。在这里给msg设置为异步消息,防止被设置的handler屏障阻拦。设置what为MSG_DO_SCHEDULE_CALLBACK。我们可以先追踪看一下处理此msg的Handler:
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
···
case MSG_DO_SCHEDULE_CALLBACK://当接受的msg为MSG_DO_SCHEDULE_CALLBACK时,即上面代码发送的msg
doScheduleCallback(msg.arg1);
break;
}
}
}
void doScheduleCallback(int callbackType) {
synchronized (mLock) {//添加内置锁
if (!mFrameScheduled) {//当前帧没有进行schedule
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {//如果当前时间有回调
scheduleFrameLocked(now);//安排当前帧绘制 最后依然调用了此方法
}
}
}
}
下面我们看一下这个安排帧绘制的方法scheduleFrameLocked
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 {
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);
···
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
上面的方法中,当开启了垂直同步时:
1 调用此方法的线程就是本Choreographer所在线程时:(为什么这么说呢,最后会解释)
直接调用scheduleVsyncLocked方法安排垂直同步绘制此帧。
2 调用线程非本Choreographer线程:
使用Handler发送MSG_DO_SCHEDULE_VSYNC的msg到队列头部,由本线程立即处理。
可以看一下handler的处理方式:
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
//
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
做了一些判断之后同样调用了scheduleVsyncLocked方法。
另一方面,当不需要进行垂直同步时:
首先计算绘制下一帧的时间,然后使用mHandler发送MSG_DO_FRAME的延时msg,在计算的绘制时间时处理。
handler的处理方式:
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);//直接调用doFrame方法。
break;
而如何计算出下一帧的绘制时间呢?
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
为上一帧绘制时间 + sFrameDelay(默认10毫秒),如果小于now (当前时间),则取当前时间。
我们先看一下不需要垂直同步的情况:
在下一帧的绘制时间直接调用doFrame方法
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.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
···
frameTimeNanos = startNanos - lastFrameOffset;//计算实际当前帧的开始时间
}
···
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);//记录当前帧信息
mFrameScheduled = false;//修改标记为,此帧所回调的内容为到目前为止被添加进来的回调,如果再有新添加的回调就会再次调用scheduleFrameLocked内的方法通知onframe了
mLastFrameTimeNanos = frameTimeNanos;//记录上一次frame渲染时间点
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
//locl
//执行此帧所有的回调
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);
}
···
}
可以看到回调是有优先级的,根据回调的类型按照CALLBACK_INPUT > CALLBACK_ANIMATION >
CALLBACK_TRAVERSAL > CALLBACK_COMMIT。
看一下doCallbacks方法
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
if (!mFrameScheduled) {//当前是否有被安排绘制的帧
return; // no work to do
}
// 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();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);//取出需要在当前时间段运行的回调
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {//如果是commmit类型,说明其他绘制回调已经执行完毕了,绘制此帧即将结束
final long jitterNanos = now - frameTimeNanos;//计算前几种回调消耗的时间
if (jitterNanos >= 2 * mFrameIntervalNanos) {//如果大于等于两帧的时间,因为下一帧绘制的开始时间可能已经安排好了,就会出现下一帧的开始时间早于此帧的结束时间。所以在这里修改其回调的结束时间。
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
···
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;//设置结束时间
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
···
c.run(frameTimeNanos);//遍历相应的callback调用run方法
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);//回收运行结束的callbackRecord
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
## Callback
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {//自定义的帧回调 会调用用户自己实现的onframe
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();//这里就是一般的情况,在token!=FRAME_CALLBACK_TOKEN 时,直接调用回调。
}
}
}
到此为止,未开启垂直同步情况下的编舞者逻辑就都看完了。
简单上一下流程图,画的有点难看,大致意思差不多
未命名文件.jpg未开启垂直同步的情况到这里就结束了,下一篇我们分析一下垂直同步的情况~~