当前位置: 首页 > news >正文

【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() 发起首次测量、布局、绘制请求。
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {// 关联 DecorViewmView = view;// 请求布局requestLayout();// ... 其他初始化逻辑
}

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

  1. 标记布局请求:通过 mLayoutRequested 标志位,确保同一帧内多次调用只触发一次布局。
  2. 调度遍历任务:调用 scheduleTraversals(),安排一次 performTraversals() 的执行。
// 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 信号到达时执行。
    //ViewRootImpl.javavoid 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 {@Overridepublic 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()创建:

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

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

  • 线程隔离性:UI 操作必须在主线程执行,Choreographer 需要与主线程的 Looper 绑定,以确保任务调度与消息循环同步。
  • 避免竞争:多线程环境下,独立实例可防止任务队列的并发冲突。
// 通过 ThreadLocal 存储每个线程的 Choreographer 实例
private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() {@Overrideprotected 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的构造方法:

    private Choreographer(Looper looper, int vsyncSource) {mLooper = looper;//使用当前线程looper创建 mHandlermHandler = new FrameHandler(looper);//USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceivermDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;mLastFrameTimeNanos = Long.MIN_VALUE;// 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16msmFrameIntervalNanos = (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:异步消息处理器
private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper); // 绑定当前线程的 Looper}@Overridepublic 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 信号接收器
// USE_VSYNC 默认为 true(Android 4.1+ 启用)
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
  • 功能:通过 JNI 层注册到显示系统,监听硬件 VSync 信号。
  • 信号回调:当 VSync 信号到达时,触发 onVsync() 方法,进而通过 Choreographer 调度帧任务。
mFrameIntervalNanos:帧间隔时间
// 60Hz 屏幕:1秒 / 60帧 ≈ 16.666ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
  • 计算依据:基于屏幕刷新率(如 60Hz)计算每帧的理想时间(16.6ms)。
  • 用途:在 doFrame() 中检测帧超时(如判断是否跳帧)。
mCallbackQueues:任务队列数组
// 五种任务类型:输入、动画、插入动画、遍历绘制、提交
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()

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 渲染任务优先执行。

    private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic 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试试。

    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;//开启了VSYNCif (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 信号。

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

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

接下来看看代码怎么说:

    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中:

    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

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

具体实现是在FrameDisplayEventReceiver中:

    private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {super(looper, vsyncSource);}@Overridepublic 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);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}

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

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

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(),执行测量、布局、绘制三大流程。

    void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;synchronized (mLock) {final long now = System.nanoTime();// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecordcallbacks = 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的runc.run(frameTimeNanos);}} finally {synchronized (mLock) {mCallbacksRunning = false;do {final CallbackRecord next = callbacks.next;//回收CallbackRecordrecycleCallbackLocked(callbacks);callbacks = next;} while (callbacks != null);}}}

任务的具体执行由 CallbackRecord.run() 完成,该函数根据 token 判断任务类型——若 tokenFRAME_CALLBACK_TOKEN(通过 postFrameCallback() 提交),则调用 FrameCallback.doFrame() 接口;否则直接执行 Runnable.run()

    private static final class CallbackRecord {public CallbackRecord next;public long dueTime;public Object action; // Runnable or FrameCallbackpublic Object token;@UnsupportedAppUsagepublic 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就是FrameCallbackpostCallbackDelayedInternal(CALLBACK_ANIMATION,callback, FRAME_CALLBACK_TOKEN, delayMillis);}public interface FrameCallback {public void doFrame(long frameTimeNanos);}

计算丢帧

举个栗子:

        //Application.javapublic void onCreate() {super.onCreate();//在Application中使用postFrameCallbackChoreographer.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);}@Overridepublic 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 工具分析耗时任务。

放一个流程图~

Android 之 Choreographer 详细分析.png

http://www.xdnf.cn/news/511633.html

相关文章:

  • Kotlin Multiplatform--04:经验总结(持续更新)
  • 系统架构设计(十四):解释器风格
  • 论信息系统项目的采购管理
  • 【周输入】510周阅读推荐-3
  • LG P9844 [ICPC 2021 Nanjing R] Paimon Segment Tree Solution
  • Python编程入门:从安装到基础算法应用的完整指南
  • weibo_comment_pc_tool | 我于2025.5月用python开发的评论采集软件,根据帖子链接爬取评论的界面工具
  • UE5无法编译问题解决
  • 机器学习(13)——LGBM(2)
  • sparkSQL读入csv文件写入mysql(2)
  • 【微信小程序 + 高德地图API 】键入关键字搜索地址,获取经纬度等
  • 餐厅等位与核酸检测排队:用算法模拟生活中的等待
  • printf在c语言中代表什么(非常详细)
  • PyTorch音频处理技术及应用研究:从特征提取到相似度分析
  • OpenCV-python数学形态学
  • 《虚拟即真实:数字人驱动技术在React Native社交中的涅槃》
  • MongoDB的安装及简单使用
  • python3GUI--智慧交通分析平台:By:PyQt5+YOLOv8(详细介绍)
  • Python面试总结
  • [Java实战]Spring Boot整合RabbitMQ:实现异步通信与消息确认机制(二十七)
  • Text2SQL:自助式数据报表开发---0517
  • Win 11开始菜单图标变成白色怎么办?
  • Java 并发编程
  • discuz X3.5批量新建用户
  • Leetcode 3551. Minimum Swaps to Sort by Digit Sum
  • BAT32 Could not stop Cortex-M device
  • 如何根据三点求圆心
  • 多模态大语言模型arxiv论文略读(八十一)
  • 【Leetcode】取余/2的幂次方
  • ABP vNext 多租户系统实现登录页自定义 Logo 的最佳实践