Android屏幕刷新机制—VSync、Choreographer-全面理解!

public static Choreographer getInstance() {
return sThreadInstance.get();
}

private static final ThreadLocal sThreadInstance =
new ThreadLocal() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
//当前线程要有looper,Choreographer实例需要传入
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;
}
};

看到这里 如你对Handler机制中looper比较熟悉的话,应该知道 Choreographer和Looper一样 是线程单例的。且当前线程要有looper,Choreographer实例需要传入。接着看看Choreographer构造方法:

private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//使用当前线程looper创建 mHandler
mHandler = new FrameHandler(looper);
//USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;

// 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

// 创建一个链表类型CallbackQueue的数组,大小为5,
//也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)
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));
}

代码中都有注释,创建了一个mHandler、VSync事件接收器mDisplayEventReceiver、任务链表数组mCallbackQueues。FrameHandler、FrameDisplayEventReceiver、CallbackQueue后面会一一说明。

4.2.2 安排任务—postCallback

回头看mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)方法,注意到第一个参数是CALLBACK_TRAVERSAL,表示回调任务的类型,共有以下5种类型:

//输入事件,首先执行
public static final int CALLBACK_INPUT = 0;
//动画,第二执行
public static final int CALLBACK_ANIMATION = 1;
//插入更新的动画,第三执行
public static final int CALLBACK_INSETS_ANIMATION = 2;
//绘制,第四执行
public static final int CALLBACK_TRAVERSAL = 3;
//提交,最后执行,
public static final int CALLBACK_COMMIT = 4;

五种类型任务对应存入对应的CallbackQueue中,每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型。

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;
//取对应类型的CallbackQueue添加任务
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

if (dueTime <= now) {
//立即执行
scheduleFrameLocked(now);
} else {
//延迟运行,最终也会走到scheduleFrameLocked()
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

首先取对应类型的CallbackQueue添加任务,action就是mTraversalRunnable,token是null。CallbackQueue的addCallbackLocked()就是把 dueTime、action、token组装成CallbackRecord后 存入CallbackQueue的下一个节点,具体代码比较简单,不再跟进。

然后注意到如果没有延迟会执行scheduleFrameLocked()方法,有延迟就会使用 mHandler发送MSG_DO_SCHEDULE_CALLBACK消息,并且注意到 使用msg.setAsynchronous(true)把消息设置成异步,这是因为前面设置了同步屏障,只有异步消息才会执行。我们看下mHandler的对这个消息的处理:

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,即绘制过程
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//申请VSYNC信号,例如当前需要绘制任务时
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//需要延迟的任务,最终还是执行上述两个事件
doScheduleCallback(msg.arg1);
break;
}
}
}

直接使用doScheduleCallback方法,看看:

void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}

发现也是走到这里,即延迟运行最终也会走到scheduleFrameLocked(),跟进看看:

private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
//开启了VSYNC
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, “Scheduling next frame on vsync.”);
}

//当前执行的线程,是否是mLooper所在线程
if (isRunningOnLooperThreadLocked()) {
//申请 VSYNC 信号
scheduleVsyncLocked();
} else {
// 若不在,就用mHandler发送消息到原线程,最后还是调用scheduleVsyncLocked方法
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 如果未开启VSYNC则直接doFrame方法(4.1后默认开启)
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, “Scheduling next frame in " + (nextFrameTime - now) + " ms.”);
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);//异步
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

  1. 如果系统未开启 VSYNC 机制,此时直接发送 MSG_DO_FRAME 消息到 FrameHandler。注意查看上面贴出的 FrameHandler 代码,此时直接执行 doFrame 方法。
  2. Android 4.1 之后系统默认开启 VSYNC,在 Choreographer 的构造方法会创建一个 FrameDisplayEventReceiver,scheduleVsyncLocked 方法将会通过它申请 VSYNC 信号。
  3. isRunningOnLooperThreadLocked 方法,其内部根据 Looper 判断是否在原线程,否则发送消息到 FrameHandler。最终还是会调用 scheduleVsyncLocked 方法申请 VSYNC 信号。

到这里,FrameHandler的作用很明显里了:发送异步消息(因为前面设置了同步屏障)。有延迟的任务发延迟消息、不在原线程的发到原线程、没开启VSYNC的直接走 doFrame 方法取执行绘制。

4.2.3 申请和接受VSync

好了, 接着就看 scheduleVsyncLocked 方法是如何申请 VSYNC 信号的。猜测肯定申请 VSYNC 信号后,信号到来时也是走doFrame() 方法,doFrame()后面再看。先跟进scheduleVsyncLocked():

private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}

很简单,调用mDisplayEventReceiver的scheduleVsync()方法,mDisplayEventReceiver是Choreographer构造方法中创建,是FrameDisplayEventReceiver 的实例。 FrameDisplayEventReceiver是 DisplayEventReceiver 的子类,DisplayEventReceiver 是一个 abstract class:

public DisplayEventReceiver(Looper looper, int vsyncSource) {
if (looper == null) {
throw new IllegalArgumentException(“looper must not be null”);
}

mMessageQueue = looper.getQueue();
// 注册VSYNC信号监听者
mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,
vsyncSource);

mCloseGuard.open(“dispose”);
}

在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:

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 {
    // 申请VSYNC中断信号,会回调onVsync方法
    nativeScheduleVsync(mReceiverPtr);
    }
    }

那么scheduleVsync()就是使用native方法nativeScheduleVsync()去申请VSYNC信号。这个native方法就看不了了,只需要知道VSYNC信号的接受回调是onVsync(),我们直接看onVsync():

/**

  • 接收到VSync脉冲时 回调
  • @param timestampNanos VSync脉冲的时间戳
  • @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
  • @param frame 帧号码,自增
    */
    @UnsupportedAppUsage
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    }

具体实现是在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, long physicalDisplayId, int frame) {
// 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;
//将本身作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息
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()中,将接收器本身作为runnable传入异步消息msg,并使用mHandler发送msg,最终执行的就是doFrame()方法了。

注意一点是,onVsync()方法中只是使用mHandler发送消息到MessageQueue中,不一定是立刻执行,如何MessageQueue中前面有较为耗时的操作,那么就要等完成,才会执行本次的doFrame()

4.2.4 doFrame

和上面猜测一样,申请VSync信号接收到后确实是走 doFrame()方法,那么就来看看Choreographer的doFrame():

void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}


// 预期执行时间
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 超时时间是否超过一帧的时间(这是因为MessageQueue虽然添加了同步屏障,但是还是有正在执行的同步任务,导致doFrame延迟执行了)
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
// 计算掉帧数
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
// 掉帧超过30帧打印Log提示
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);
// Frame标志位恢复
mFrameScheduled = false;
// 记录最后一帧时间
mLastFrameTimeNanos = frameTimeNanos;
}

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);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

上面都有注释了很好理解,接着看任务的具体执行doCallbacks 方法:

void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {

final long now = System.nanoTime();
// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;

//提交任务类型
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos

  • mFrameIntervalNanos;
    if (DEBUG_JANK) {
    Log.d(TAG, "Commit callback delayed

    by " + (jitterNanos * 0.000001f)
  • " ms which is more than twice the frame interval of "
  • (mFrameIntervalNanos * 0.000001f) + " ms! "
  • "Setting frame time to " + (lastFrameOffset * 0.000001f)
  • " ms in the past.");
    mDebugPrintNextFrameTimeDelta = true;
    }
    frameTimeNanos = now - lastFrameOffset;
    mLastFrameTimeNanos = frameTimeNanos;
    }
    }
    }
    try {
    // 迭代执行队列所有任务
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
    // 回调CallbackRecord的run,其内部回调Callback的run
    c.run(frameTimeNanos);
    }
    } finally {
    synchronized (mLock) {
    mCallbacksRunning = false;
    do {
    final CallbackRecord next = callbacks.next;
    //回收CallbackRecord
    recycleCallbackLocked(callbacks);
    callbacks = next;
    } while (callbacks != null);
    }
    }
    }

主要内容就是取对应任务类型的队列,遍历队列执行所有任务,执行任务是 CallbackRecord的 run 方法:

private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;

@UnsupportedAppUsage
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通过postFrameCallback 或 postFrameCallbackDelayed,会执行这里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
//取出Runnable执行run()
((Runnable)action).run();
}
}
}

前面看到mChoreographer.postCallback传的token是null,所以取出action,就是Runnable,执行run(),这里的action就是 ViewRootImpl 发起的绘制任务mTraversalRunnable了,那么这样整个逻辑就闭环了

那么 啥时候 token == FRAME_CALLBACK_TOKEN 呢?答案是Choreographer的postFrameCallback()方法:

public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException(“callback must not be null”);
}

//也是走到是postCallbackDelayedInternal,并且注意是CALLBACK_ANIMATION类型,
//token是FRAME_CALLBACK_TOKEN,action就是FrameCallback
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}

可以看到postFrameCallback()传入的是FrameCallback实例,接口FrameCallback只有一个doFrame()方法。并且也是走到postCallbackDelayedInternal,FrameCallback实例作为action传入,token则是FRAME_CALLBACK_TOKEN,并且任务是CALLBACK_ANIMATION类型。

Choreographer的postFrameCallback()通常用来计算丢帧情况,使用方式如下:

//Application.java
public void onCreate() {
super.onCreate();
//在Application中使用postFrameCallback
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
}

public class FPSFrameCallback implements Choreographer.FrameCallback {

private static final String TAG = “FPS_TEST”;
private long mLastFrameTimeNanos = 0;
private long mFrameIntervalNanos;

public FPSFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
mFrameIntervalNanos = (long)(1000000000 / 60.0);
}

@Override
public void doFrame(long frameTimeNanos) {

//初始化时间
if (mLastFrameTimeNanos == 0) {
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if(skippedFrames>30){
//丢帧30以上打印日志
Log.i(TAG, "Skipped " + skippedFrames + " frames! "

  • “The application may be doing too much work on its main thread.”);
    }
    }
    mLastFrameTimeNanos=frameTimeNanos;
    //注册下一帧回调
    Choreographer.getInstance().postFrameCallback(this);
    }
    }

4.2.5 小结

使用Choreographer的postCallback()、postFrameCallback() 作用理解:发送任务 存队列中,监听VSync信号,当前VSync到来时 会使用mHandler发送异步message,这个message的Runnable就是队列中的所有任务。

好了,Choreographer整个代码逻辑都讲完了,引用《Android 之 Choreographer 详细分析》的流程图:

五、Handler异步消息与同步屏障

最后来介绍下异步消息与同步屏障。

在Handler中,Message分为3种:同步消息、异步消息、同步屏障消息,他们三者都是Message,只是属性有些区别。

5.1异步消息

通常我们使用创建Handler方式如下:

public Handler() {
this(null, false);
}

注意到内部使用了两个两个参数的构造方法,其中第二个是false:

public Handler(@Nullable Callback callback, boolean async) {

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    //异步标志

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

通常我们使用创建Handler方式如下:

public Handler() {
this(null, false);
}

注意到内部使用了两个两个参数的构造方法,其中第二个是false:

public Handler(@Nullable Callback callback, boolean async) {

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()

  • " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    //异步标志

最后

给大家分享一份移动架构大纲,包含了移动架构师需要掌握的所有的技术体系,大家可以对比一下自己不足或者欠缺的地方有方向的去学习提升;

[外链图片转存中…(img-Icf4I3Aq-1716051813541)]

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值