Android卡顿真的是因为”掉帧“,已拿offer入职

mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

// 向 Choreographer 抛 View树遍历任务

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

if (!mUnbufferedInputDispatch) {

scheduleConsumeBatchedInput();

}

notifyRendererOfFramePending();

pokeDrawLockIfNeeded();

}

}

// 遍历 View 树任务

final class TraversalRunnable implements Runnable {

@Override

public void run() {

doTraversal();

}

}

// 构建遍历 View 树任务实例

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

}

ViewRootImpl通过抛任务到Choreographer来触发 View 树遍历。

Choreographerandroid.view包下的一个类,字面意思是“编舞者”,隐含着“需要将两者同步”的意思,编舞即是将动作和节奏同步。而Choreographer是将"绘制内容"和"垂直同步信号"同步。

存放绘制任务


public final class Choreographer {

// 输入任务

public static final int CALLBACK_INPUT = 0;

// 动画任务

public static final int CALLBACK_ANIMATION = 1;

// view树遍历任务

public static final int CALLBACK_TRAVERSAL = 2;

// COMMIT任务

public static final int CALLBACK_COMMIT = 3;

// 暂存任务的链式数组

private final CallbackQueue[] mCallbackQueues;

// 主线程消息处理器

private final FrameHandler mHandler;

// 抛绘制任务

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;

// 1. 将绘制任务根据类型暂存在链式结构中

mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

// 2. 订阅下一个垂直同步信号

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);

}

}

}

// 主线程消息处理器

private final class FrameHandler extends Handler {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case MSG_DO_SCHEDULE_CALLBACK:

// 在未来时间点订阅垂直同步信号

doScheduleCallback(msg.arg1);

break;

}

}

}

void doScheduleCallback(int callbackType) {

synchronized (mLock) {

if (!mFrameScheduled) {

final long now = SystemClock.uptimeMillis();

if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {

// 订阅下一个垂直同步信号

scheduleFrameLocked(now);

}

}

}

}

}

Choreographer接收到新的绘制任务后,会执行两个动作:

  1. 绘制任务入链:

public final class Choreographer {

// 绘制任务链

private final class CallbackQueue {

// 任务链头结点

private CallbackRecord mHead;

// 绘制任务入链(按时间升序)

public 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;

}

}

// 绘制任务结点

private static final class CallbackRecord {

// 下一个绘制任务

public CallbackRecord next;

// 绘制任务应该在这个时刻被执行

public long dueTime;

// 描述绘制任务的代码段

public Object action;

// 执行绘制任务

public void run(long frameTimeNanos) {

if (token == FRAME_CALLBACK_TOKEN) {

((FrameCallback)action).doFrame(frameTimeNanos);

} else {

((Runnable)action).run();

}

}

}

}

Choreographer接收有四种任务类型,分别是输入、动画、View树遍历、COMMIT。每个任务被抽象成CallbackRecord,同类任务按时间先后顺序组成一条任务链CallbackQueue。四条任务链存放在mCallbackQueues[]数组结构中。

  1. 订阅下一个垂直同步信号

public final class Choreographer {

private void scheduleFrameLocked(long now) {

// 若已经订阅过下个垂直同步信号,则什么也不做

if (!mFrameScheduled) {

// 当下一个垂直同步信号到来时,需要执行绘制任务

mFrameScheduled = true;

if (USE_VSYNC) {

// 不管走哪个分支,最终都会调用scheduleVsyncLocked()来注册接收垂直同步信号

if (isRunningOnLooperThreadLocked()) {

scheduleVsyncLocked();

} else {

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);

msg.setAsynchronous(true);

mHandler.sendMessageAtFrontOfQueue(msg);

}

} else {

}

}

}

// 委托 DisplayEventReceiver 来注册垂直同步信号

private void scheduleVsyncLocked() {

mDisplayEventReceiver.scheduleVsync();

}

// 绘制一帧

void doFrame(long frameTimeNanos, int frame) {

synchronized (mLock) {

// 若不需要响应这个垂直同步信号,则直接返回,该帧不绘制任何东西

if (!mFrameScheduled) {

return;

}

}

}

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {

// 垂直同步信号回调

@Override

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

// 向主线程抛任务,绘制一帧

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);

}

}

}

// 垂直同步信号接收器

public abstract class DisplayEventReceiver {

// 注册接收下一个垂直同步信号

public void scheduleVsync() {

if (mReceiverPtr == 0) {

Log.w(TAG, “…”);

} else {

// 向SurfaceFlinger订阅下一个垂直同步信号

nativeScheduleVsync(mReceiverPtr);

}

}

}

并不是每个垂直同步信号都会被上层订阅并处理,只有当Choreographer订阅了下一个垂直同步信号,SurfaceFlinger才会把信号通过onVsync()回调给上层。

图中第一个垂直信号到来之前,上层调用了postCallback()Choreographer抛绘制任务,同时订阅了下一个信号并把mFrameScheduled置为 true,表示需要绘制下一帧。当第一个信号产生时,onVsync()被回调,同时将doFrame()抛到主线程执行,执行完毕后将mFrameScheduled置为 false,因没有后续订阅动作,所以上层不会收到后续的onVsync()回调,也不会绘制任何新东西。

执行绘制任务


ViewRootImplChoreographer抛绘制任务后,任务并没有立马执行,而是被暂存在绘制任务链中,并注册接收下一个垂直同步信号。只有当下一个信号通过onVsync()回调后,它才会被执行:

public final class Choreographer {

// 垂直同步信号接收器

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {

private boolean mHavePendingVsync;

private long mTimestampNanos;

private int mFrame;

@Override

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

// 发送异步消息到主线程,执行当前的Runnable,即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()

public final class Choreographer {

void doFrame(long frameTimeNanos, int frame) {

final long startNanos;

synchronized (mLock) {

// 如果没有订阅这一帧的垂直同步信号,则直接退出不绘制

if (!mFrameScheduled) {

return; // no work to do

}

try {

// 处理这一帧的输入事件

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

// 处理这一帧的动画

doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

// 处理这一帧的 View 树遍历

doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

// 所有绘制任务结束后执行 COMMIT 任务

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

} finally {

}

}

}

绘制每一帧时,会按照“输入事件”、“动画”、“View 树遍历”、“COMMIT”这样的顺序处理任务。

处理函数doCallback()定义如下:

public final class Choreographer {

// 暂存绘制任务的链式数组

private final CallbackQueue[] mCallbackQueues;

void doCallbacks(int callbackType, long frameTimeNanos) {

CallbackRecord callbacks;

synchronized (mLock) {

final long now = System.nanoTime();

// 从指定类型的任务链中根据当前时间点取出要执行的任务

callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);

if (callbacks == null) {

return;

}

}

try {

// 执行任务链上被取出的所有任务

for (CallbackRecord c = callbacks; c != null; c = c.next) {

c.run(frameTimeNanos);

}

} finally {

}

}

// 任务实体类

private static final class CallbackRecor

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

d {

public CallbackRecord next;

public Object action;

public void run(long frameTimeNanos) {

if (token == FRAME_CALLBACK_TOKEN) {

((FrameCallback)action).doFrame(frameTimeNanos);

} else {

// 执行任务

((Runnable)action).run();

}

}

}

}

追踪到这,ViewRootImpl推送过来的 “View 树遍历” 任务总算被执行了。

其中extractDueCallbacksLocked()是任务链CallbackQueue的方法,用于获取待当前时间点需要被执行的所有任务:

private final class CallbackQueue {

// 任务链头结点

private CallbackRecord mHead;

// 获取当前时间节点之前的所有任务,这些任务都需要在当前帧被执行

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;

}

}

绘制当前帧时,会以当前时刻为分割线,从任务链上摘取所有以前的任务,并按时间先后顺序逐个执行。

在纸上演算一遍上面的代码:

上图表示在第一个垂直同步信号还未到来时,上层已经向任务链中已添加了 3 个任务,其中前两个的执行时刻在第一个信号到来之前,而第三个任务在第一个信号后执行。

在第一个任务入链时就完成了对第一个信号的订阅,而第三个任务在第一个信号之后执行,所以它的订阅行为doScheduleCallback()先被存放在主线程消息队列中,待第一个信号到来之后再订阅第二个信号。

当第一个垂直同步信号到来后,doFrame()被抛到主线程,它从任务链中摘取当前时间节点之前的任务1和2并执行它们。当任务被执行完,就从主线程消息队列中取出下一条消息“为任务3订阅下一个垂直同步信号”并执行。所有这些都完成后,主线程只能发呆,等待下一个垂直同步信号。

当第二个垂直同步信号到来之后,任务链中剩下的任务3都被取出并抛到主线程执行。任务链空了,当任务3执行完毕后,主线程彻底没事做,只能等到上层再向Choreographer抛任务。

卡顿的原因是掉帧?


推迟

上述情况,绘制任务都能在一个帧间隔内完成,若任务很耗时,执行时间超过了帧间隔会怎么样?

第一个垂直信号到来后,任务1和2被抛到主线程执行,这次它们执行时间略长,超过了一个帧间隔,导致订阅下一个信号的函数迟迟未被执行。对于上层来说,错过了第二个onVsync()回调,这意味着,任务1和2错过了一次显示时机,任务3错过了一次渲染时机。对于底层来说,显示器在发出垂直同步信号时会向图形缓冲取显示内容,这次没拿到内容,只能继续显示上一帧图像。

任务1和2执行完,主线程才“订阅下一个信号”,当第三个信号到来时,显示器从图形缓冲中取到了任务1和2的渲染结果,而任务3也被抛到主线程执行。

这个 case 中,任务链中所有的绘制任务都被推迟显示了。

合并

若主线程中除了绘制任务外,还有别的耗时任务,情况会怎么样?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值