Android Choreographer 源码分析Android 教你如何发现 APP 卡顿

Android Choreographer 源码分析Android 教你如何发现 APP 卡顿,第1张

概述Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,

Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制 *** 作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms , Vsync 信号唤醒 Choreographer 来做 App 的绘制 *** 作,这就是引入 Choreographer 的主要作用。了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解

主线程运行机制的本质

在介绍 Choreographer 之前,我们先理一下 AndroID 主线程运行的本质,其实就是 Message 的处理过程,我们的各种 *** 作,包括每一帧的渲染,手势 *** 作 ,都是通过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,如下图所示

MethodTrace 图示

Systrace 图示

可以发现,每一帧时间都是固定的。所以一旦一个 Message 的处理时间超过了 16.6ms 就会引起卡顿。关于如何发现卡顿,可以参考文章:

Android 教你如何发现 APP 卡顿Choreographer 简介

Choreographer 扮演 AndroID 渲染链路中承上启下的角色

承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 input(主要是 input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等 *** 作) ,判断卡顿掉帧情况,记录 CallBack 耗时等

启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FramedisplayEventReceiver.onVsync );请求 Vsync(FramedisplayEventReceiver.scheduleVsync) .

从上面可以看出来, Choreographer 担任的是一个工具人的角色,他之所以重要,是因为通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 AndroID App 可以以一个稳定的帧率运行(目前大部分是 60fps),减少帧率波动带来的不适感。

Choreographer 的工作流程

Choreographer 初始化SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调 FramedisplayEventReceiver.onVsync,进入 Choreographer 的主处理函数 doFrame

初始化 FrameHandler ,绑定 Looper

初始化 FramedisplayEventReceiver ,与 SurfaceFlinger 建立通信用于接收和请求 Vsync

初始化 CallBackQueues

Choreographer.doFrame 计算掉帧逻辑

Choreographer.doFrame 处理 Choreographer 的第一个 callback : input

Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation

Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation

Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversalChoreographer.doFrame 处理 Choreographer 的第五个 callback : commit ?

traversal-draw 中 UIThread 与 RenderThread 同步数据

RenderThread 处理绘制数据,真正进行渲染

将渲染好的 Buffer swap 给 SurfaceFlinger 进行合成

Choreographer 源码分析Choreographer 的单例初始化
    // Thread local storage for the choreographer.    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;        }    };

这里采用的是 ThreadLocal 来构造单例,这样每个线程都会有一个属于自己的 choreographer 实例。

接下去看 choreographer 的构造函数

    private Choreographer(Looper looper,int vsyncSource) {        mLooper = looper;        mHandler =  FrameHandler(looper);
     // 这里可以发现只有在为 true 的时候才会使用 vsync mdisplayEventReceiver
= USE_VSYNC ? FramedisplayEventReceiver(looper,vsyncSource) : ; mLastFrameTimeNanos = Long.MIN_VALUE;     // 每一帧的间隔是根据刷新频率来的 mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
     // 给每一种回调类型都创建了一个队列
for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = CallbackQueue(); } b/68769804: For low FPS experiments. setFPSdivisor(SystemPropertIEs.getInt(ThreadedRenderer.DEBUG_FPS_divISOR,1)); }

这里做了几个初始化 *** 作,根据Looper对象生成,Looper和线程是一对一的关系,对应上面说明里的每个线程对应一个Choreographer。

初始化FrameHandler。接收处理消息。

初始化FramedisplayEventReceiver。FramedisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,一般为60HZ,用来控制系统同步 *** 作,怎么同ChoreoGrapher一起工作的,将在下文介绍。

初始化mLastFrameTimeNanos(标记上一个frame的渲染时间)以及mFrameIntervalNanos(帧率,fps,一般手机上为1s/60)。

初始化CallbackQueue,callback队列,将在下一帧开始渲染时回调。

接下去看看 FrameHandler 和 FramedisplayEventReceiver 的结构。

    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(System.nanoTime(),0);                    break;                 MSG_DO_SCHEDulE_VSYNC:                    doScheduleVsync();                     MSG_DO_SCHEDulE_CALLBACK:                    doScheduleCallback(msg.arg1);                    ;            }        }    }

看上面的代码,就是一个简单的Handler。处理3个类型的消息。

MSG_DO_FRAME:开始渲染下一帧的 *** 作

MSG_DO_SCHEDulE_VSYNC:请求 Vsync 信号

MSG_DO_SCHEDulE_CALLBACK:请求执行 callback

下面再细分一下,分别详细看一下这三个步骤是怎么实现的。

FramedisplayEventReceiver
    class FramedisplayEventReceiver  displayEventReceiver            implements Runnable {        boolean mHavePendingVsync;        long mTimestampNanos;         mFrame;        public FramedisplayEventReceiver(Looper looper,1)"> vsyncSource) {            (looper,vsyncSource);        }         Todo(b/116025192): physicaldisplayID is ignored because SF only emits VSYNC events for         the internal display and displayEventReceiver#scheduleVsync only allows requesting VSYNC         for the internal display implicitly.        @OverrIDe        voID onVsync(long timestampNanos,1)">long physicaldisplayID,1)"> 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;            Message msg = Message.obtain(mHandler,1)">this);            msg.setAsynchronous();            mHandler.sendMessageAtTime(msg,timestampNanos / TimeUtils.NANOS_PER_MS);        }        @OverrIDe         run() {            mHavePendingVsync = false;            doFrame(mTimestampNanos,mFrame);        }    }
FramedisplayEventReceiver 继承自 displayEventReceiver,同时也实现了Runnable 接口,是处于 Choreographer 中的私有内部类。当接收到底层的 VSync 信号开始处理 UI 过程。VSync 信号由 SurfaceFlinger 实现并定时发送。FramedisplayEventReceiver 收到信号后,调用 onVsync 方法组织消息发送到主线程处理。这个消息主要内容就是 run 方法里面的 doFrame 了,这里 mTimestampNanos 是信号到来的时间参数。

那么 FramedisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FramedisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下

 androID/vIEw/Choreographer.java vsyncSource) {    mLooper = looper;    mdisplayEventReceiver = USE_VSYNC            ? ;    ......}
vsyncSource) { androID/vIEw/displayEventReceiver.javapublic displayEventReceiver(Looper looper,1)"> vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<displayEventReceiver>(),mMessageQueue,vsyncSource);}

nativeInit 后续的代码可以自己跟一下,可以对照这篇文章和源码,由于篇幅比较多,这里就不细说了。

简单来说,FramedisplayEventReceiver 的初始化过程中,通过 BitTube (本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 displayEventdispatcher ,displayEventdispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FramedisplayEventReceiver.onVsync ,触发开始一帧的绘制。

如下图

ChoreoGrapher 的总体流程

FrameHandler 和 FramedisplayEventReceiver 是怎么工作的呢?ChoreoGrapher 的总体流程图如下图(拷贝的图片):

 以上是总体的流程图:

PostCallBack or postFrameCallback发起添加回调,这个FrameCallBack将在下一帧被渲染时执行。

AddToCallBackQueue,将 FrameCallBack 添加到回调队列里面,等待时机执行回调。每种类型的callback按照设置的执行时间(dueTime)顺序排序分别保存在一个单链表中。

判断 FrameCallBack设定的执行时间是否在当前时间之后,若是,发送 MSG_DO_SCHEDulE_CALLBACK 消息到主线程,安排执行doScheduleCallback,安排执行CallBack。否则直接跳到第4步。

执行 scheduleFrameLocked,安排执行下一帧。

判断上一帧是否已经执行,若未执行,当前 *** 作直接结束。若已经执行,根据情况执行以下6、7步。

若使用垂直同步信号进行同步,则执行7.否则,直接跳到9。

若当前线程是UI线程,则通过执行scheduleVsyncLocked请求垂直同步信号。否则,送MSG_DO_SCHEDulE_VSYNC消息到主线程,安排执行doScheduleVsync,在主线程调用scheduleVsyncLocked。

收到垂直同步信号,调用FramedisplayEventReceiver.onVsync(),发送消息到主线程,请求执行doFrame。

执行doFrame,渲染下一帧。

主要的工作在 doFrame 中,接下来我们具体看看 doFrame 函数都干了些什么。
从名字看很容易理解 doFrame 函数就是开始进行下一帧的显示工作。好了以下源代码又来了,我们一行一行分析一下吧。

doFrame
    @UnsupportedAppUsage    voID doFrame(long frameTimeNanos,1)"> frame) {         startNanos;        synchronized (mlock) {
       // 为false,说明还未开始
if (!mFrameScheduled) { return; no work to do } if (DEBUG_JANK && mDeBUGPrintNextFrametimedelta) { mDeBUGPrintNextFrametimedelta = ; Log.d(TAG,"Frame time delta: " + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime();
       // 计算当前时间与 vsync 信号的时间差
long jitterNanos = startNanos - frameTimeNanos;
       // 说明出现掉帧情况,注意只有 jitterNanos 大于 16.6 ms 才说明掉帧,否则只是轻微的延迟。
if (jitterNanos >= mFrameIntervalNanos) { long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_liMIT) { Log.i(TAG,"Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); }
          // 当发生掉帧后,需要计算被耽误的时间。比如处理了 36.6ms,一个周期是 16.6 ms,相当于延迟了 3.4 ms 执行
long lastFrameOffset = jitterNanos % (DEBUG_JANK) { Log.d(TAG,"Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "SkipPing " + skippedFrames + " frames and setting frame " + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."          // 修正当前帧的时间 = 开始时间 - 耽误时间 frameTimeNanos = startNanos - lastFrameOffset; }       // 当前时间小于前一帧时间,不执行 *** 作 if (frameTimeNanos < mLastFrameTimeNanos) {           // 直接请求下一个 vsync 信号 scheduleVsyncLocked(); ; }       // 大于 1 说明采用的默认帧数的一半,因此需要根据时间间隔来判断是否有必要执行绘制 if (mfpSdivisor > 1long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (mFrameIntervalNanos * mfpSdivisor) && timeSinceVsync > 0) {
            // 时间间隔小于指定的时间,继续请求下一个 vsync 信号 scheduleVsyncLocked();
; } }       // 保存当前帧的相关信息 mFrameInfo.setVsync(intendedFrameTimeNanos,frameTimeNanos); mFrameScheduled = ; mLastFrameTimeNanos = frameTimeNanos; } try {
       // 执行相关 callbacks 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); } (DEBUG_FRAMES) { long endNanos = System.nanoTime(); Log.d(TAG,"Frame " + frame + ": Finished,took " + (endNanos - startNanos) * 0.000001f + " ms,latency " + (startNanos - frameTimeNanos) * 0.000001f + " ms."); } } 

总结起来其实主要是两个 *** 作:

设置当前 frame 的启动时间

判断是否跳帧,若跳帧修正当前 frame 的启动时间到最近的 VSync 信号时间。如果没跳帧,当前 frame 启动时间直接设置为当前 VSync 信号时间。修正完时间后,无论当前 frame 是否跳帧,使得当前 frame 的启动时间与 VSync 信号还是在一个节奏上的,可能延后了一到几个周期,但是都是在下一个 vsync 信号到来才进行处理 。

如下图所示是时间修正的一个例子,


 没有跳帧但延迟 

由于第二个 frame 执行超时,第三个 frame 实际启动时间比第三个 VSync 信号到来时间要晚,因为这时候延时比较小,没有超过一个时钟周期,系统还是将 frameTimeNanos3 传给回调,回调拿到的时间和 VSync 信号同步。

再来看看下图:

跳帧

由于第二个 frame执行时间超过 2 个时钟周期,导致第三个 frame 延后执行时间大于一个时钟周期,系统认为这时候影响较大,判定为跳帧了,将第三个 frame 的时间修正为 frameTimeNanos4,比 VSync 真正到来的时间晚了一个时钟周期。

时间修正,既保证了doFrame *** 作和 VSync 保持同步节奏,又保证实际启动时间与记录的时间点相差不会太大,便于同步及分析。

顺序执行callBack队列里面的callback

然后接下来看看 doCallbacks 的执行过程:

    voID doCallbacks(int callbackType,1)"> frameTimeNanos) {        CallbackRecord callbacks;         (mlock) {             We use "Now" to determine when callbacks become due because it's possible             for earlIEr processing phases in a frame to post callbacks that should run             in a following phase,such as an input event that causes an animation to start.             System.nanoTime();
       // callbacks
= mCallbackQueues[callbackType].extractDueCallbacksLocked( Now / TimeUtils.NANOS_PER_MS); if (callbacks == ; } mCallbacksRunning = ; Update the frame time if necessary when committing the frame. We only update the frame time if we are more than 2 frames late reaching the commit phase. This ensures that the frame time which is observed by the callbacks will always increase from one frame to the next and never repeat. We never want the next frame's starting frame time to end up being less than or equal to the prevIoUs frame's commit frame time. Keep in mind that the next frame has most likely already been scheduled by Now so we play it safe by ensuring the commit time is always at least one frame behind.
       // commit 类型是最后执行的,如果此时发现前面处理时间过长,就会进行纠正。
if (callbackType == Choreographer.CALLBACK_COMMIT) { long jitterNanos = Now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW,"jitterNanos",() jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { mFrameIntervalNanos + mFrameIntervalNanos; (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 = ; } frameTimeNanos = Now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } { Trace.traceBegin(Trace.TRACE_TAG_VIEW,CALLBACK_TRACE_TitleS[callbackType]); for (CallbackRecord c = callbacks; c != null; c = c.next) { (DEBUG_FRAMES) { Log.d(TAG,"runcallback: type=" + callbackType + ",action=" + c.action + ",token=" + c.token + ",latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); }
          // 运行每一个 callback c.run(frameTimeNanos); } }
{ (mlock) { mCallbacksRunning = do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != ); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }

callback的类型有以下 4 种,除了文章一开始提到的 3 种外,还有一个 CALLBACK_COMMIT。

CALLBACK_input:输入

CALLBACK_ANIMATION:动画

CALLBACK_TRAVERSAL:遍历,执行 measure、layout、draw

CALLBACK_COMMIT:遍历完成的提交 *** 作,用来修正动画启动时间

然后看上面的源码,分析一下每个 callback 的执行过程:

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

得到执行时间在当前时间之前的所有 CallBack,保存在单链表中。每种类型的 callback 按执行时间先后顺序排序分别存在一个单链表里面。为了保证当前 callback 执行时新 post 进来的 callback 在下一个 frame 时才被执行,这个地方 extractDueCallbacksLocked 会将需要执行的 callback 和以后执行的 callback 断开变成两个链表,新 post 进来的 callback 会被放到后面一个链表中。当前 frame 只会执行前一个链表中的 callback,保证了在执行 callback 时,如果callback中Post相同类型的callback,这些新加的 callback 将在下一个 frame 启动后才会被执行。

2. 接下来,看一大段注释,如果类型是 CALLBACK_COMMIT,并且当前 frame 渲染时间超过了两个时钟周期,则将当前提交时间修正为上一个垂直同步信号时间。为了保证下一个frame 的提交时间和当前 frame 时间相差为一且不重复。
这个地方注释挺难看懂,实际上这个地方 CALLBACK_COMMIT 是为了解决 ValueAnimator 的一个问题而引入的,主要是解决因为遍历时间过长导致动画时间启动过长,时间缩短,导致跳帧,这里修正动画第一个 frame 开始时间延后来改善,这时候才表示动画真正启动。为什么不直接设置当前时间而是回溯一个时钟周期之前的时间呢?看注释,这里如果设置为当前 frame 时间,因为动画的第一个 frame 其实已经绘制完成,第二个 frame 这时候已经开始了,设置为当前时间会导致这两个 frame 时间一样,导致冲突。

详细情况请看官方针对这个问题的修改。Fix animation start jank due to expensive layout operations.

如下图所示:

 修正commit时间

比如说在第二个frame开始执行时,开始渲染动画的第一个画面,第二个frame执行时间超过了两个时钟周期,Draw *** 作执行结束后,这时候完成了动画第一帧的渲染,动画实际上还没开始,但是时间已经过了两个时钟周期,后面动画实际执行时间将会缩短一个时钟周期。这时候系统通过修正commit时间到frameTimeNanos的上一个VSync信号时间,即完成动画第一帧渲染之前的VSync信号到来时间,修正了动画启动时间,保证动画执行时间的正确性。

调用 c.run(frameTimeNanos) 执行回调
    class CallbackRecord {         CallbackRecord next;         dueTime;        public Object action;  Runnable or FrameCallback         Object token;        @UnsupportedAppUsage        voID run( frameTimeNanos) {            if (token == FRAME_CALLBACK_TOKEN) {                ((FrameCallback)action).doFrame(frameTimeNanos);            }  {                ((Runnable)action).run();            }        }    }

CallbackRecord 的代码如上所示。

发起绘制的请求

doFrame 的逻辑了解清楚了,但是关于发起 vsync 请求的逻辑却没有讲。

 VIEwRootImpl     @UnsupportedAppUsage     scheduleTraversals() {
     // 如果已经请求绘制了,就不会再次请求,因为多次请求,只有有一个执行就满足要求了
mTraversalScheduled) { mTraversalScheduled = ;
       // 同步 mTraversalbarrIEr
= mHandler.getLooper().getQueue().postsyncbarrIEr();
       // 发送一个 callback 用于在下一帧来临时候处理 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,
null); mUnbufferedinputdispatch) { scheduleConsumeBatchedinput(); }
       // 通知稍候绘制 notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

 接着会调用 postCallbackDelayed:

    voID postCallbackDelayed( callbackType,Runnable action,Object token, delayMillis) {        if (action == ) {            new IllegalArgumentException("action must not be null");        }        if (callbackType < 0 || callbackType > CALLBACK_LAST) {            new IllegalArgumentException("callbackType is invalID");        }        postCallbackDelayedInternal(callbackType,action,token,delayMillis);    }

主要是做一些逻辑判断,确保传入的是对的。 

接着又会调用 postCallbackDelayedInternal,保存 callback,并发起请求下一帧。

    voID postCallbackDelayedInternal( (DEBUG_FRAMES) {            Log.d(TAG,"PostCallback: type=" + callbackType                    + ",action=" + action + ",1)"> token                    + ",delayMillis=" + delayMillis);        }         SystemClock.uptimeMillis();            long dueTime = Now + delayMillis;            mCallbackQueues[callbackType].addCallbackLocked(dueTime,token);        // 小于当前时间,说明需要立即执行            if (dueTime <= Now) {                scheduleFrameLocked(Now);            }           // 发送一个延迟 msg                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDulE_CALLBACK,action);                msg.arg1 = callbackType;                msg.setAsynchronous();                mHandler.sendMessageAtTime(msg,dueTime);            }        }    }

简单来说,就是判断当前是否有必要发起一个绘制请求,比如你发了一个 500ms 后重绘的消息,对于这个消息,会在 500ms 后在进行处理。但如果不是延迟消息,那说明需要立即处理。但是对于 vIEw 的绘制逻辑,必须得等到下一个 vsync 到来的时候才会真正进行绘制。

接下来看看 scheduleFrameLocked 的逻辑:

   voID scheduleFrameLocked( Now) {        mFrameScheduled) {            mFrameScheduled = ;             (USE_VSYNC) {                );                }                 If running on the Looper thread,then schedule the vsync immediately,1)"> otherwise post a message to schedule the vsync from the UI thread                 as soon as possible.
         // 如果是在一个 looper 线程中,那么直接执行请求就好 (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); }             // 如果是在主线程,那么需要发送一个请求 vsync 的消息,并插到最前面,需要确保前一个消息处理完后在开始请求 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDulE_VSYNC); msg.setAsynchronous(); mHandler.sendMessageAtFrontOfQueue(msg); } }           // 如果不用 vsync 信号,那么就可以直接执行,只是需要记录每一帧的时间 long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay,Now); ); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous( doScheduleVsync() { (mFrameScheduled) { scheduleVsyncLocked(); } } } @UnsupportedAppUsage scheduleVsyncLocked() { 发起获取 vsync 信号的请求 mdisplayEventReceiver.scheduleVsync(); } /** * Schedules a single vertical sync pulse to be delivered when the next * display frame begins. */ @UnsupportedAppUsage scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG,"Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } { nativeScheduleVsync(mReceiverPtr); } }
到这里,就把 Choreographer 的基本原理都讲完了。
 源码小结

Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定

displayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IdisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,displayEventReceiver 的 onVsync 函数将被调用。

displayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。

Choreographer 定义了一个 FrameCallbackinterface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 AndroID Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。

Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:ListVIEw 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面,这取决于

CALLBACK_input : 处理输入事件处理有关

CALLBACK_ANIMATION : 处理 Animation 的处理有关

CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调

CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关

CALLBACK_COMMIT : 处理 Commit 相关回调

CALLBACK_input 、CALLBACK_ANIMATION 会修改 vIEw 的属性,所以要比 CALLBACK_TRAVERSAL 先执行

 最后附上一张时序图,来回顾一下整个流程
 另外,知道源码后,也就知道如何去发现 app 卡顿情况了。 详见 Android 教你如何发现 APP 卡顿

 

参考文章Android 基于 Choreographer 的渲染机制详解Android Choreographer 源码分析 总结

以上是内存溢出为你收集整理的Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿全部内容,希望文章能够帮你解决Android Choreographer 源码分析 Android 教你如何发现 APP 卡顿所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1121327.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存