【Android】从Choreographer到UI渲染(二)

【Android】从Choreographer到UI渲染(二)

Google 在 2012 年推出的 Project Butter(黄油计划)是 Android 系统发展史上的重要里程碑,旨在解决长期存在的 UI 卡顿、响应延迟等问题,提升用户体验。

在 Android 4.1 之前,系统缺乏统一的帧同步机制,屏幕刷新、应用渲染与用户输入之间常因时序错乱导致画面撕裂(Screen Tearing )或帧丢失(Jank)。

例如,双缓冲机制下,若 GPU 渲染超时,CPU 无法及时处理下一帧,导致后续帧被迫跳过多个刷新周期,用户会明显感知卡顿;触摸事件的处理也因未与屏幕刷新同步,出现"不跟手"的延迟。这些问题严重影响了用户体验,尤其在动画和滚动场景中尤为突出。

为此,Google 通过 VSync 垂直同步三重缓冲(Triple Buffering)Choreographer 调度框架 三管齐下重构显示系统:VSync 信号将 CPU/GPU 的渲染周期与屏幕刷新严格对齐,确保每帧的准备工作在 16ms(60Hz 屏幕)内启动;三重缓冲通过增加临时缓冲区缓解 GPU 超时导致的连续丢帧问题,牺牲少量内存换取流畅性;而 Choreographer 作为"舞蹈编导",统一调度输入、动画、绘制等任务,在 VSync 信号到达时批量执行,避免任务碎片化。

垂直同步和三缓冲已经在之前的篇章中提到过,所以今天我们详细分析一下Choregrapher。

本文参考:

Android 之 Choreographer 详细分析 - 简书

何时调用Choreographer

在 Android 的 UI 渲染机制中,无论是 Activity 启动后的首次布局,还是后续通过动画或手动触发的界面更新,最终都会汇聚到 ViewRootImplscheduleTraversals() 方法。

我们首先来回忆一下Activity的启动与Window的添加过程。

当 Activity 完成 onResume() 后,系统会将其视图层级(DecorView)添加到 Window 中。具体步骤如下:

  1. Window 创建 :Activity 的 Window(通常是 PhoneWindow)在 onCreate() 阶段初始化,并在 onResume() 后通过 WindowManager 添加到系统。
  2. ViewRootImpl 关联WindowManagerGlobal.addView() 方法会创建 ViewRootImpl 实例,并调用其 setView() 方法,将 DecorView 与 ViewRootImpl 绑定。
  3. 触发首次布局 :在 ViewRootImpl.setView() 中,调用 requestLayout() 发起首次测量、布局、绘制请求。
java 复制代码
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    // 关联 DecorView
    mView = view;
    // 请求布局
    requestLayout();
    // ... 其他初始化逻辑
}

requestLayout() 是 UI 更新的起点,其核心逻辑如下:

  1. 标记布局请求 :通过 mLayoutRequested 标志位,确保同一帧内多次调用只触发一次布局。
  2. 调度遍历任务 :调用 scheduleTraversals(),安排一次 performTraversals() 的执行。
java 复制代码
// ViewRootImpl.java
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

scheduleTraversals() 负责协调 UI 更新与 VSync 信号的同步:

  1. 同步屏障(Sync Barrier) :通过 postSyncBarrier() 向主线程的 MessageQueue 插入一个同步屏障,屏蔽普通同步消息,确保 UI 更新任务优先执行。
  2. 提交回调到 Choreographer :将 mTraversalRunnable(最终触发 doTraversal())提交给 Choreographer,在下一个 VSync 信号到达时执行。
java 复制代码
    //ViewRootImpl.java
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            //此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
            mTraversalScheduled = true;
            //添加同步屏障,屏蔽同步消息,保证VSync到来立即执行绘制
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //mTraversalRunnable是TraversalRunnable实例,最终走到run(),也即doTraversal();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            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);
            ...
            //开始三大绘制流程
            performTraversals();
            ...
        }
    }

Choreographer的创建

它的实例mChoreographer,是在ViewRootImpl的构造方法内使用Choreographer.getInstance()创建:

java 复制代码
Choreographer mChoreographer;

//ViewRootImpl实例是在添加window时创建
public ViewRootImpl(Context context, Display display) {
    ...
    mChoreographer = Choreographer.getInstance();
    ...
}

Choreographer 通过 ThreadLocal 实现线程单例,确保每个线程(尤其是主线程)拥有独立的实例。这种设计类似于 Looper 的线程绑定机制,原因如下:

  • 线程隔离性 :UI 操作必须在主线程执行,Choreographer 需要与主线程的 Looper 绑定,以确保任务调度与消息循环同步。
  • 避免竞争:多线程环境下,独立实例可防止任务队列的并发冲突。
java 复制代码
// 通过 ThreadLocal 存储每个线程的 Choreographer 实例
private static final ThreadLocal<Choreographer> sThreadInstance = 
    new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            // 必须绑定到带有 Looper 的线程
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("当前线程必须拥有 Looper!");
            }
            // 创建实例并与 Looper 关联
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            // 如果是主线程,记录为主实例
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

public static Choreographer getInstance() {
    return sThreadInstance.get(); // 返回当前线程的实例
}

关键点

  • 强制 Looper 存在 :若线程没有 Looper(如未调用 Looper.prepare()),直接抛出异常。这解释了为什么 UI 操作必须在主线程(主线程默认初始化了 Looper)。
  • 主线程标识 :通过 Looper.getMainLooper() 判断当前线程是否为主线程,并记录主实例供全局访问。

紧接着是Chroreographer的构造方法:

java 复制代码
    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));
    }

Choreographer 的构造函数初始化了多个关键组件,这些组件共同协作完成帧调度:

FrameHandler:异步消息处理器
java 复制代码
private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper); // 绑定当前线程的 Looper
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME: // 执行帧任务
                doFrame(System.nanoTime(), 0);
                break;
            case MSG_DO_SCHEDULE_VSYNC: // 请求 VSync 信号
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK: // 延迟任务调度
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}
  • 作用 :处理与帧调度相关的异步消息(如 MSG_DO_FRAME),确保任务在主线程执行。
  • 异步消息 :通过 msg.setAsynchronous(true) 标记消息为异步,绕过同步屏障(后文详述)。
FrameDisplayEventReceiver:VSync 信号接收器
java 复制代码
// USE_VSYNC 默认为 true(Android 4.1+ 启用)
mDisplayEventReceiver = USE_VSYNC 
    ? new FrameDisplayEventReceiver(looper, vsyncSource) 
    : null;
  • 功能:通过 JNI 层注册到显示系统,监听硬件 VSync 信号。
  • 信号回调 :当 VSync 信号到达时,触发 onVsync() 方法,进而通过 Choreographer 调度帧任务。
mFrameIntervalNanos:帧间隔时间
java 复制代码
// 60Hz 屏幕:1秒 / 60帧 ≈ 16.666ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
  • 计算依据:基于屏幕刷新率(如 60Hz)计算每帧的理想时间(16.6ms)。
  • 用途 :在 doFrame() 中检测帧超时(如判断是否跳帧)。
mCallbackQueues:任务队列数组
java 复制代码
// 五种任务类型:输入、动画、插入动画、遍历绘制、提交
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
    mCallbackQueues[i] = new CallbackQueue();
}
  • 任务分类:将回调任务按类型存入不同队列,确保执行顺序:

    CALLBACK_INPUT:处理触摸/输入事件(优先级最高)。

    CALLBACK_ANIMATION:执行属性动画、过渡动画。

    CALLBACK_INSETS_ANIMATION:窗口插入动画(如状态栏/导航栏变化)。

    CALLBACK_TRAVERSAL :执行 View 的测量、布局、绘制。

    CALLBACK_COMMIT:提交帧数据到渲染线程(最后执行)。

Choreographer调用流程

根据不同的任务类型分派任务

当调用 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, ...) 提交任务时,系统根据任务类型(如 CALLBACK_TRAVERSAL 表示绘制任务)将任务存入对应的 CallbackQueue 队列。

postCallback()内部调用postCallbackDelayed(),接着又调用postCallbackDelayedInternal()

java 复制代码
private void postCallbackDelayedInternal(int callbackType, Object action, long delayMillis) {
    synchronized (mLock) {
        // 计算任务的到期时间
        final long dueTime = SystemClock.uptimeMillis() + delayMillis;
        // 将任务添加到对应类型的队列
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, null);

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

postCallbackDelayedInternal() 方法会根据延迟时间决定立即调度或延迟处理:若任务需要立即执行,直接调用 scheduleFrameLocked() 申请 VSync 信号;若存在延迟,则通过 FrameHandler 发送异步消息 MSG_DO_SCHEDULE_CALLBACK,最终仍会触发 scheduleFrameLocked()。异步消息的标记(msg.setAsynchronous(true))是关键设计,它允许这些消息绕过同步屏障------在 ViewRootImpl.scheduleTraversals() 中插入的同步屏障会阻塞普通同步消息,确保 UI 渲染任务优先执行。

java 复制代码
    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;
            }
        }
    }

进第三个case试试。

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

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

java 复制代码
    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);
            }
        }
    }

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

申请与接受VSync

Choreographer 会通过 scheduleVsyncLocked() 方法向系统申请 VSync 信号。

java 复制代码
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

这一过程的核心在于 FrameDisplayEventReceiver,也就是此处的mDisplayEventReceiver,它是 DisplayEventReceiver 的子类,在构造时通过 JNI 层与底层显示服务建立连接(nativeInit),注册为 VSync 信号的监听者。当调用 scheduleVsync() 时,实际通过 nativeScheduleVsync 这一本地方法向系统请求下一次 VSync 中断信号。一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiveronVsync 方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true))发送到主线程的 MessageQueue

接下来看看代码怎么说:

java 复制代码
    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<DisplayEventReceiver>(this), mMessageQueue,vsyncSource);

        mCloseGuard.open("dispose");
    }

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

FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:

java 复制代码
    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() 时,实际通过 nativeScheduleVsync 这一本地方法向系统请求下一次 VSync 中断信号。

一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiveronVsync 方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true))发送到主线程的 MessageQueue

java 复制代码
    /**
     * 接收到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中:

java 复制代码
    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);
        }
    }

当主线程处理到这条异步消息时,会执行 FrameDisplayEventReceiverrun() 方法,进而调用 Choreographer.doFrame(),这是整个渲染流程的起点。

doFrame执行过程

java 复制代码
    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);
        }
    }

doFrame() 中,系统首先通过计算当前时间与 VSync 信号时间戳的差值(jitterNanos = startNanos - frameTimeNanos)判断是否发生跳帧:若差值超过一帧的理论时长(如 16.6ms 对应 60Hz 屏幕),则按超出时长除以单帧时间计算跳帧数,若超过 30 帧(约 500ms)则打印警告日志,提示主线程可能存在耗时操作。

随后,系统按固定顺序处理任务队列------依次执行输入事件(CALLBACK_INPUT)、动画更新(CALLBACK_ANIMATION)、窗口插入动画(CALLBACK_INSETS_ANIMATION)、视图树遍历(CALLBACK_TRAVERSAL)和帧提交(CALLBACK_COMMIT)。

每个队列的任务通过 doCallbacks() 方法提取并执行:例如 CALLBACK_TRAVERSAL 队列中的任务通常是 ViewRootImpl 提交的 mTraversalRunnable,其 run() 方法最终触发 performTraversals(),执行测量、布局、绘制三大流程。

java 复制代码
    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() 完成,该函数根据 token 判断任务类型------若 tokenFRAME_CALLBACK_TOKEN(通过 postFrameCallback() 提交),则调用 FrameCallback.doFrame() 接口;否则直接执行 Runnable.run()

java 复制代码
    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()方法:

java 复制代码
    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);
    }

计算丢帧

举个栗子:

java 复制代码
        //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);
      }
  }

通过 postFrameCallback() 注册一个 FrameCallback,在每次 doFrame() 时计算相邻两次 VSync 信号的时间差,若超过阈值则判定为跳帧。例如,示例中的 FPSFrameCallback 在每次回调时比较当前帧与上一帧的时间差,若超过 16.6ms 的倍数,则累加跳帧数并打印警告。

这种设计使得所有 UI 操作(无论是触摸、动画还是绘制)都被严格对齐到 VSync 信号,既避免了画面撕裂,又为每一帧提供了完整的处理窗口,而开发者可通过监听回调精准定位主线程卡顿的根源,例如在 doFrame 中插入性能埋点或结合 Systrace 工具分析耗时任务。

放一个流程图~

相关推荐
feiyangqingyun10 分钟前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户2018792831674 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子4 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82275 小时前
安卓接入Max广告源
android
齊家治國平天下5 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO5 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel5 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢5 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖5 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉6 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql