在 Android 开发过程中,我们经常谈到布局优化、卡顿优化时,通常都知道要减少布局层级、避免主线程做耗时操作等等,这样可以减少丢帧,如果丢帧比较严重的话就会发生明显的卡顿现象。这篇文章我们来详细分析 Android 屏幕刷新机制,便于优化我们的项目。
在一个典型的显示系统中,一般包括 CPU、GPU、显示器三个部分, CPU 负责计算帧数据,把计算好的数据交给 GPU ,GPU 会对图形数据进行渲染,渲染好后放到 buffer (图像缓冲区)里缓存起来,然后显示器负责把 buffer 里的数据呈现到屏幕上。
1. 屏幕刷新频率
一秒内屏幕刷新的次数(一秒内显示了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。刷新频率取决于硬件的固定参数(不会变的)。
2. 逐行扫描
显示器并不是一次性将画面显示到屏幕上,而是从左到右边,从上到下逐行扫描,顺序显示整屏的一个个像素点,不过这一过程快到人眼无法察觉到变化。以 60 Hz 刷新率的屏幕为例,这一过程即 1000 / 60 ≈ 16ms。
3. 帧率
表示 GPU 在一秒内绘制操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运行的非常流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟 GPU 最多绘制 60 帧画面。帧率是动态变化的,例如当画面静止时,GPU 是没有绘制操作的,屏幕刷新的还是 buffer 中的数据,即 GPU 最后操作的帧数据。
4. 双缓冲机制
如果使用单缓冲很容易造成画面撕裂,因为屏幕刷新频是固定的,比如每16.6ms 从 buffer 取数据显示完一帧,理想情况下帧率和刷新频率保持一致,即每绘制完成一帧,显示器显示一帧。但是 CPU/GPU 写数据是不可控的,所以会出现 buffer 里有些数据根本没显示出来就被重写了,即 buffer 里的数据可能是来自不同的帧的数据, 当屏幕刷新时,此时它并不知道 buffer 的状态,因此从 buffer 抓取的帧并不是完整的一帧画面,即出现画面撕裂。
因此使用双缓冲机制就可以完美解决画面撕裂的问题,双缓冲让绘制和显示器拥有各自的 buffer,GPU 始终将完成的一帧图像数据写入到 Back Buffer,而显示器使用 Frame Buffer,当屏幕刷新时,Frame Buffer 并不会发生变化,当Back buffer准备就绪后,它们才进行交换。
5. VSync
如是 Back buffer 准备完成一帧数据以后就进行,那么如果此时屏幕还没有完整显示上一帧内容的话,肯定是会出问题的。看来只能是等到屏幕处理完一帧数据后,才可以执行这一操作了。
当扫描完一个屏幕后,设备需要重新回到第一行以进入下一次的循环,此时有一段时间空隙,称为VerticalBlanking Interval(VBI)。这个时间点就是我们进行缓冲区交换的最佳时间。因为此时屏幕没有在刷新,也就避免了交换过程中出现 screen tearing 的状况。
VSync (垂直同步)是 VerticalSynchronizatio n的简写,它利用 VBI 时期出现的 vertical sync pulse(垂直同步脉冲)来保证双缓冲在最佳时间点才进行交换。另外,交换是指各自的内存地址,可以认为该操作是瞬间完成。
在 Android4.1 之前,屏幕刷新也遵循上面介绍的 双缓冲 + VSync 机制。如下图:
以时间的顺序来看下将会发生的过程:
Display 显示第0帧数据,此时 CPU 和 GPU 渲染第1帧画面,且在Display显示下一帧前完成
因为渲染及时,Display在第0帧显示完成后,也就是第1个VSync后,缓存进行交换,然后正常显示第1帧
接着第2帧开始处理,是直到第2个VSync快来前才开始处理的。
第2个 VSync 来时,由于第2帧数据还没有准备就绪,缓存没有交换,显示的还是第1帧。这种情况被Android开发组命名为 “Jank” ,即发生了丢帧。
当第2帧数据准备完成后,它并不会马上被显示,而是要等待下一个 VSync 进行缓存交换再显示。
上面的问题是多显示了一次第一帧画面,原因是第2帧的 CPU/GPU 计算没能在 VSync 信号到来前完成 。
我们知道,双缓存的交换是在 Vsync 到来时进行,交换后屏幕会取 Frame buffer 内的新数据,那么此时的 Back buffer 就可以供 GPU 准备下一帧数据了。 如果 Vsync 到来时 CPU/GPU 就开始操作的话,是有完整的16.6ms 的时间来处理数据的,这样应该会基本避免jank的出现了(除非CPU/GPU计算超过了16.6ms)。 那如何让 CPU/GPU 计算在 Vsync 到来时进行呢?
为了优化显示性能,Google在Android 4.1系统中对 Android Display 系统进行了重构,实现了 Project Butter(黄油工程):系统在收到 VSync pulse 后,将马上开始下一帧的渲染。即一旦收到 VSync 通知(16ms 触发一次),CPU和GPU 立刻开始计算然后把数据写入 buffer。如下图
CPU/GPU 根据 VSYNC 信号同步处理数据,可以让 CPU/GPU 有完整的16ms 的时间来处理数据,减少了 jank 。
问题又来了,如果界面比较复杂,CPU/GPU 的处理时间较长,超过了16.6ms呢?如下图:
在第二个时间段内,但却因 GPU 还在处理 B 帧,缓存没能交换,导致 A 帧被重复显示。
而B完成后,又因为缺乏VSync pulse信号,它只能等待下一个 signal 的来临。于是在这一过程中,有一大段时间是被浪费的。
当下一个 VSync 出现时,CPU/GPU 马上执行操作(A帧),且缓存交换,相应的显示屏对应的就是B。这时看起来就是正常的。只不过由于执行时间仍然超过16ms,导致下一次应该执行的缓冲区交换又被推迟了——如此循环反复,便出现了越来越多的“Jank”。
为什么 CPU 不能在第二个 16ms 处理绘制工作呢?
原因是只有两个 buffer,Back buffer 正在被 GPU 用来处理B帧的数据, Frame buffer 的内容用于 Display 的显示,这样两个 buffer 都被占用,CPU 则无法准备下一帧的数据。 那么,如果再提供一个buffer,CPU、GPU 和显示设备都能使用各自的 buffer 工作,互不影响。
三缓存就是在双缓冲机制基础上增加了一个 Graphic Buffer 缓冲区,这样可以最大限度的利用空闲时间,缺点是多使用的一个 Graphic Buffer 所占用的内存。
第一个Jank,是不可避免的。但是在第二个 16ms 时间段,CPU/GPU 使用 第三个 Buffer 完成C帧的计算,虽然还是会多显示一次 A 帧,但后续显示就比较顺畅了,有效避免 Jank 的进一步加剧。
注意在第3段中,A帧的计算已完成,但是在第4个 VSync 来的时候才显示,如果是双缓冲,那在第三个VSync就可以显示了。
三缓冲有效利用了等待 VSync 的时间,减少了 Jank,但是带来了延迟。 所以,是不是 Buffer 越多越好呢?这个是否定的,Buffer 正常还是两个,当出现 Jank 后三个足以。
以上就是Android屏幕刷新的原理了。
上面讲到,Google 在 Android 4.1 系统中对 Android Display 系统进行了优化:在收到 VSync pulse 后,将马上开始下一帧的渲染。本节就来讲 "drawing with VSync" 的实现——Choreographer。
Choreographer,意为 舞蹈编导、编舞者。在这里就是指对CPU/GPU 绘制的指导 —— 收到VSync信号 才开始绘制,保证绘制拥有完整的16.6ms,避免绘制的随机性。
Choreographer 是用来协调动画、输入和绘图的计时
通常应用层不会直接使用Choreographer,而是使用更高级的API,例如动画和View绘制相关的ValueAnimator.start()、View.invalidate()等。
业界一般通过Choreographer来监控应用的帧率。
学习 Choreographer 可以帮助理解每帧运行的原理,也可加深对 Handler 机制、View 绘制流程的理解,这样再去做 UI 优化、卡顿优化,思路会更清晰。
Activity 启动流程走完 onResume 方法后,会进行 Window 的添加。Window 添加过程会调用 ViewRootImpl 的 setView() 方法,setView() 方法会调用 requestLayout() 方法来请求绘制布局,requestLayout() 方法内部又会走到 scheduleTraversals() 方法,最后会走到 performTraversals() 方法,接着到了我们熟知的测量、布局、绘制三大流程了。
另外,查看源码发现,当我们使用 ValueAnimator.start()、View.invalidate() 时,最后也是走到 ViewRootImpl 的scheduleTraversals() 方法。(View.invalidate() 内部会循环获取 ViewParent 直到 ViewRootImpl 的invalidateChildInParent() 方法,然后走到 scheduleTraversals() ,可自行查看源码 )。
因此:所有 UI 的变化都是走到 ViewRootImpl 的 scheduleTraversals() 方法。
说明:源码基于Android API 30 进行分析。
接下来我们从 ViewRootImpl 的 scheduleTraversals 方法开始分析,代码如下所示:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
// 用来保证同一时间多次更改只会刷新一次。
mTraversalScheduled = true;
// 添加同步屏障,屏蔽同步消息,保证VSync信号到来立即执行绘制
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// mTraversalRunnable 是 TraversalRunnable 的实例,最终走到 run 方法,然后调用 doTraversal 方法。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
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");
}
// 开始View绘制的三大流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
主要逻辑说明:
首先使用 mTraversalScheduled 字段保证同时间多次更改只会刷新一次。
然后向当前线程的消息队列 MessageQueue 中添加了同步屏障,这样就屏蔽了正常的同步消息,保证 VSync 到来后立即执行绘制,而不是要等前面的同步消息。后面会具体分析同步屏障和异步消息的代码逻辑。
调用了 mChoreographer.postCallback() 方法,发送一个会在下一帧执行的回调,即在下一个 VSync 到来时会执行 TraversalRunnable --> run() --> doTraversal() ---> performTraversals() --> View的绘制流程。
接下来,就是分析的重点 —— Choreographer。我们先看它的实例 mChoreographer ,是在 ViewRootImpl 的构造方法内使用 Choreographer.getInstance() 创建:
// ViewRootImpl 实例是在添加 Window 时创建
public ViewRootImpl(Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
...
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
...
}
接下来查看 Choreographer.getInstance() 的方法,代码如下所示:
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) {
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;
}
};
Choreographe 是线程单例的。且当前线程要有 looper,Choreographer 实例化的时候需要传入。
接着看看 Choreographer 构造方法:
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 使用当前线程的 looper 创建 Handler
mHandler = new FrameHandler(looper);
// USE_VSYNC 在Android4.1以上默认是true,表示具备接受VSync的能力,
// 这个接受能力就是FrameDisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 计算一帧的时间,单位纳秒
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 创建一个链表类型的 CallbackQueue 数组,大小为5
// 也就是数组中有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));
}
下面我们回到 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 将首先处理 CALLBACK_INPUT 类型的任务,然后是 CALLBACK_ANIMATION 类型,最后才是 CALLBACK_COMMIT 类型。
postCallback() 方法内部会调用 postCallbackDelayed(),接着又调用 postCallbackDelayedInternal() 方法,代码如下所示:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + 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 {
// 延迟执行,最终也会走到 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 的下一个节点,代码如下:
@UnsupportedAppUsage
public void addCallbackLocked(long dueTime, Object action, Object token) {
// 将 dueTime, action, token 组装成 CallbackRecord
// CallbackRecord 任务链表(单链表的数据结构)
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
// 链表头结点
CallbackRecord entry = mHead;
// 如果链表头结点为null,就将当前的 callback 设置为头结点。
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;
}
在上面的 postCallbackDelayedInternal 方法中,注意,如果没有延迟会执行 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;
if (USE_VSYNC) {
// 开启了 VSYNC,Android 4.1 默认开启了 VSYNC
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on 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.
// 当前执行的线程是否是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 方法(
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);
}
}
}
FrameHandler 的作用:发送异步消息(因为前面设置了同步屏障消息)。有延迟的任务发延迟消息、不在原线程的发到原线程、没开启 VSYNC 的直接走 doFrame 方法去执行绘制。
接下来跟踪 scheduleVsyncLocked 方法,研究它是如何申请 VSYNC 信号的,代码如下所示:
@UnsupportedAppUsage
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
调用 mDisplayEventReceiver 的 scheduleVsync() 方法,mDisplayEventReceiver 是 Choreographer 构造方法中创建,实际上是 FrameDisplayEventReceiver 的实例。 FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类,DisplayEventReceiver 是一个抽象类。
public DisplayEventReceiver(Looper looper, int vsyncSource, int configChanged) {
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
// 获取looper的消息队列
mMessageQueue = looper.getQueue();
// 注册 VSYNC 信号监听器
mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,
vsyncSource, configChanged);
mCloseGuard.open("dispose");
}
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 信号
nativeScheduleVsync(mReceiverPtr);
}
}
在 DisplayEventReceiver 的构造方法会通过 native 方法创建 VSYNC 信号的监听者。然后在 scheduleVsync 方法中也是通过 native 方法去申请 VSYNC 信号,这里就不分析这些 native 方法了,我们只需要知道 VSYNC 信号的接收回调是 onVsync 方法即可,接下来我们查看 onVsync 方法,在 DisplayEventReceiver 类中是一个空方法,具体实现在 FrameDisplayEventReceiver 类中,代码如下:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
...
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
...
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);
}
}
接下来我们分析 doFrame 方法,代码如下所示:
@UnsupportedAppUsage
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
...
// 预期执行的时间
long intendedFrameTimeNanos = frameTimeNanos;
// 当前时间
startNanos = System.nanoTime();
// 超时时间
final long jitterNanos = startNanos - frameTimeNanos;
// 超时时间是否超过了一帧的时间
// 这是因为 MessageQueue 虽然添加了同步屏障,但是还是有正在执行的异步任务
// 导致 doFrame 延迟执行了。
if (jitterNanos >= mFrameIntervalNanos) {
// 计算掉帧数
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 掉帧超过30帧打打印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;
...
frameTimeNanos = startNanos - lastFrameOffset;
}
...
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 恢复标志位
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();
// 根据指定的类型重 CallbackQueues 中查找到到达执行时间的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;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
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 {
// 遍历执行链表中的所有任务
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
// 回调 CallbackRecord 的run方法,其内部回调 Callback 的 run方法。
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
主要内容就是取对应任务类型的队列,遍历链表执行所有任务,执行任务是 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);
}
可以看到 postFrameCallback() 传入的是 FrameCallback 实例,接口 FrameCallback 只有一个 doFrame() 方法。并且也是走到 postCallbackDelayedInternal ,FrameCallback 实例作为 action 传入,token则是FRAME_CALLBACK_TOKEN,并且任务是 CALLBACK_ANIMATION 类型。
Choreographer 的 postFrameCallback() 通常用来计算丢帧情况,使用方式如下:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 使用 postFrameCallback 计算丢帧情况
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));
}
class FPSFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = "FPS_Callback";
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);
}
}
}
在 Handler 中 , Message 分为三种类型: 同步消息、异步消息、同步屏障消息,他们三者都是 Message,只是属性有些区别而已。
我们通常使用 Handler 的时候都是 new Handler() 的方法,其构造方法如下所示:
@Deprecated
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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;
// 异步消息标志
mAsynchronous = async;
}
无参构造方法中传的 false 表示非异步消息,也就是同步消息,mAsynchronous 属性的使用是在 enqueueMessage 方法中的,代码如下所示:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 将 Handler 设置给 msg 的 target 属性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 如果为异步消息,通过 msg.setAsynchronous(true) 将消息设置为异步。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue 的 postSyncBarrier() 方法就是用来插入一个屏障到消息队列的,代码如下所示:
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
// 获取消息,但是这里并没有给msg的target属性赋值。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
从这个方法我们可以知道如下:
同步屏障消息和普通消息的区别在于屏障消息没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而同步屏障消息不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
同步屏障消息和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
postSyncBarrier() 返回一个int类型的数值,通过这个数值可以撤销屏障,即 removeSyncBarrier()。
postSyncBarrier() 是 hide方法,我们无法调用(反射在Android 9 之前可以,Android 9 之后禁止反射 hide方法),它是留给系统使用的。插入普通消息会唤醒消息队列,但是插入同步屏障消息不会。
那么 ,同步屏障消息是如何挡住普通同步消息的呢? 我们看看 MessageQueue 的 next 方法,代码如下所示:
@UnsupportedAppUsage
Message next() {
...
for (;;) {
...
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 由上面的分析可知,当msg不为空,但是msg的target为null时,表示该消息是同步屏障消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 遍历查找异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
}
其实很简单,遍历消息队列时,发现了同步屏障消息,那么就只取异步消息了。