原文链接 https://juejin.cn/post/7291935035530313755
本文部分图文参考自:https://juejin.cn/post/6863756420380196877#heading-19
在开始讲解屏幕刷机制前,先回顾一下 View 的绘制流程,读过前面的文章 从点击桌面图标到应用界面展示 应该都有印象,View 绘制流程开启的地方就是 ViewRootImpl
的 scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 添加同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 加入一个 callback 到 ****Choreographer****
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
可以看到 scheduleTraversals()
主要做了两件事:
mTraversalRunnable
加入到 mChoreographer
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 可以看到这里出现了熟悉的 performTraversals,这里面封装了 onMeasure、onLayout、onDraw
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
可见,TraversalRunnable
就是一个 Runnable
,里面做了两件事情:
那 TraversalRunnable 是什么时候被执行呢,这就引出本文的主角: Choreographer
Choreographer
翻译为编舞者,负责从显示系统接收脉冲信号,编排渲染下一帧的绘制工作,负责获取 Vsync 同步信号并控制 UI 线程完成图像绘制的类。
怎么理解这一句话呢? 一次完整的绘制,是需要 CPU、GPU 和显示设备的配合,但是三者是一个并行运作的状态,那怎么相互协调呢?所以引进了 VSync 信号机制:每 16ms,硬件(或者软件)会发出一个 VSync 信号,CPU 接收到这个信号,开始了一次绘制流程。再下一次 VSync 信号到来之时,Display 就可以直接显示第一帧,CPU 也开始绘制第二帧。就这样循环下去。
也就是说 CPU 和 GPU 必须要在这一次的 VSync 信号发生和下一次 VSync 信号到来之前要准备好这一帧的数据,否则就发生了掉帧的现象了。
可以看下图,大致可以了解 VSync 信号机制:
// 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);
可以看到主要是两个动作:
在 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 的机制,不做解释,详见 Android 消息机制。
在 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) {
// 通过 postFrameCallback 或 postFrameCallbackDelayed,会执行这里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
// 这里也即是调用了 TraservalRunnable 的 run 方法,也即是三个绘制流程
((Runnable)action).run();
}
}
可见当 vsync 信号来临的时候,主要做了两件事情
TraservalRunnable
的三大绘制流程这里是不是说,Choreographer
是不是只要注册一次以后,都可以收到 Vsync
信号呢?
其实不是的,Vsync 信号需要每次都去注册,而且只能接收到一次。这样能保证在不需要重新绘制的情况下,Choreographer 也就不需要接收信号,就不会去注册 Vsync 信号。
requestLayout
只会注册一次并且收到一次 VSYNCdoFrame
中请求下一次 VSYNCpublic void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
// 如果表头是空的,就把传入的任务当成表头
if (entry == null) {
mHead = callback;
return;
}
// 如果传入的任务时间小于表头任务,就把任务插在前面,新任务成为表头
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
// 否则遍历链表,把新任务按照时间从小到大插入链表
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}
extractDueCallbacksLocked
把所有当前时间需要被执行的任务都取出来,构成一个新的链表 public CallbackRecord extractDueCallbacksLocked(long now) {
CallbackRecord callbacks = mHead;
// 如果表头为空,或者时间没有到需要执行的时间,直接返回空链表
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
CallbackRecord last = callbacks;
CallbackRecord next = last.next;
// 遍历链表,找到所有当前时间可被执行的任务,由于链表已经是从小到大排序
// 所以只需要两个指针,一个指向表头,一个指向时间大于当前时间的结点就可以了
while (next != null) {
if (next.dueTime > now) {
// 找到了新链表的结束位置
last.next = null;
break;
}
last = next;
next = next.next;
}
// 新表头指向截断的位置,截断位置之前的所有结点构成的链表被返回
mHead = next;
// 返回新链表表头位置
return callbacks;
}
ViewRootImpl.scheduleTraversals()
添加同步屏障,添加一个任务到 Choreographer
内的队列,这个任务里面调用 performTraversals
Choreographer
的队列是一个单链表FrameDisplayEventReceiver
向 JNI 注册 VSYNC 信号,当 VSYNC 到来时,JNI 调用 onVsync()
,FrameDisplayEventReceiver
→onVsync()
通过 Handler 发送异步消息到主线程(这个线程是初始化 Choreographer 的线程,可以是主线程也可以是其他线程,每个线程都可以有一个 Choreographer 对象)FrameDisplayEventReceiver
→ 主线程处理 doFrame()
performTraversals
,然后移除同步屏障在 ViewRootImpl 的构造方法里面
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
//...
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
// ...
}
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;
}
};
public static Choreographer getInstance() {
return sThreadInstance.get(); // ThreadLocal.get 方法会回调 initialValue() 来创建实例
}
Choreographer 是线程内单例的
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
}
可以看到五种 callback 类型的优先级是
CALLBACK_INPUT 、CALLBACK_ANIMATION 会修改 view 的属性,所以要先于 CALLBACK_TRAVERSAL 执行Choreographer 监控帧率的原理
前面提到过
android.view.Choreographer#doFrame()
方法来处理逻辑mCallbackQueues
里面取出 Choreographer.*CALLBACK_TRAVE*RSAL
对应的队列(是一个单向链表) private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
由 requestLayout()
或者 invalidate()
触发的流程,token 是空的
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 这里 token 是空的
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
所以,CallbackRecord 的 run 方法会执行到 action.run()
, 也就是后面会走到 android.view.ViewRootImpl#performTraversals()
;
但是如果是通过 android.view.Choreographer#postFrameCallback
触发的,token 传的是 FRAME_CALLBACK_TOKEN
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");
}
// 这里 token 是 FRAME_CALLBACK_TOKEN,注意这了的 callbackType 是 CALLBACK_ANIMATION
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
所以,CallbackRecord 的 run 方法会执行到 ((FrameCallback)action).doFrame(frameTimeNanos)
; 也就是执行 postFrameCallback
传入的 callback 的 doFrame 方法;因此也可以用来监听帧率
// 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);
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。