Choreographer--编舞者源码分析

一.首先介绍一些基础知识

  1. 刷新率(Refresh Rate):

   刷新率代表屏幕在一秒内刷新屏幕的次数,用赫兹来表示。赫兹是频率的单位,一秒震动的次数。这个刷新率取决于硬件固定的参数。这个值一般是60Hz。即每16.66ms刷新一次屏幕。

 2.帧速率(Frame Rate ):

  帧速率代表了GPU在一秒内绘制操作的帧数。比如30FPS、60FPS。Frame Per Second。

3.如果两个设备独立运行,如果刷新率和帧速率不同步,会引发以下两种问题。
如果帧速率高于刷新率,制作的频率大于展示的频率,会导致屏幕图像的展示的跳跃,跳帧的现象。
如果帧速率小于刷新率,制作的频率小于展示的频率,会导致屏幕图像的展示的中断,掉帧的现象。

4.android为了解决上面的问题,在4.1版本中引入了Projectbuffer.
ProjectBuffer对Android Display系统进行了重构,引入了三个核心元素,即Vsync,TripleBuffer和Choreographer。
其中Vsync是Vertical Synchronization 垂直同步是缩写。是一种在PC上已经很早就广泛使用的技术。

引入是Vsync来进行控制CPUGPU的绘制和屏幕刷新同步进行。

而编舞者choreography的引入,主要是配合Vsync,给上层App的渲染提供一个稳定的时机。Vsync到来的时候,Choreographer可以根据Vsync信号,统一管理应用的输入、动画、绘制等任务的执行情况。Choreographer就像一个指挥家一样,来把控着UI的绘制,所以取名编舞者。

二、android源码中Choreographer是如何运行的。

   1.首先在ViewRootImpl构造函数中创建了Choreographer对象

 public ViewRootImpl(Context context, Display display) {
     mChoreographer = Choreographer.getInstance();
 }
  public static Choreographer getInstance() {
         return sThreadInstance.get();
  }

  当调用get时,如果为null,会调用initialValue()方法。并把Choreographer实例和ThreadLocal绑定。

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        //因为后面会用到handler通讯,所以必须有一个Looper循环
        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);
        //如果是主线程,则把choreographer赋值给mMainInstance
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};

2.看Choreographer构造函数

   mLastFrameTimeNanos:记录上一帧绘制的时间。

   mFrameIntervalNanos:屏幕绘制一帧的时间间隔,这个是纳秒值。如果屏幕刷新率是60Hz,那么刷新一帧的时间间隔就是16.66.......毫秒。

private Choreographer(Looper looper, int vsyncSource) {
    // 传一个Looper进来,
    mLooper = looper;
    //用来处理消息的。
    mHandler = new FrameHandler(looper);
    //USE_VSYNC 是否使用Vsync
    //boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);
     mDisplayEventReceiver = USE_VSYNC
                    ? new FrameDisplayEventReceiver(looper, vsyncSource)
                    : null;
    //上一帧绘制的时间
    mLastFrameTimeNanos = Long.MIN_VALUE;

    //1秒是1000毫秒,1毫秒是1000微秒,1微秒是1000纳秒
    //1秒就是1*1000*1000*1000=10的九次方纳秒
    //绘制一帧的时间间隔----纳秒。如果是60Hz,那么刷新一帧展示的时间就是16.66毫秒。
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

    //初始化回调队列,后面会从这个回调队列中取出Runnable执行run方法。
     mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }

}

获取屏幕的刷新率:

//屏幕的刷新率,一秒钟可以刷新屏幕多少次,通常是60Hz
private static float getRefreshRate() {
    DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
            Display.DEFAULT_DISPLAY);
    return di.getMode().getRefreshRate();
}

3.初始化工作完成,那么Choreographer是怎么跑起来的,入口函数在哪?

  对于UI绘制来说是入口在RootViewImpl的scheduleTraversals()方法中。

void scheduleTraversals() {
 if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //发送一个屏障消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //注意第一个参数CALLBACK_TRAVERSAL,回调函数的类型。
            //mTraversalRunnable 回调函数要执行的runnable。
            //第三个参数token,传了一个null
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
 }
//第一个参数callbackType 有五种类型,这几个回调是有顺序的。
1.CALLBACK_INPUT 输入回调,首先运行
2.CALLBACK_ANIMATION 动画回调,这个在将动画原理的时候,会看到
3.CALLBACK_INSETS_ANIMATION inset和update相关的动画,运行在上面两个回调之后,
4.CALLBACK_TRAVERSAL 遍历回调,用于处理布局和绘制
5.CALLBACK_COMMIT Commit回调,在Traversal绘制回调之后。

接下来看postCallbackDelayedInternal方法

第二个参数就是上面的mTraversalRunnable。
第四个参数延迟的时间,这里延迟时间是0,没有延迟
所以这个方法走if判断的第一个分支,
private void postCallbackDelayedInternal(int callbackType,
         Object action, Object token, long delayMillis) {

     synchronized (mLock) {
         final long now = SystemClock.uptimeMillis();
         final long dueTime = now + delayMillis;
         //将runnable加入回调队列
         mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
         上面传过来的delayMillis是0,所以走第一个分支。
         if (dueTime <= now) {
             scheduleFrameLocked(now);
         } else { //如果有延迟,则发送一个延迟的异步消息。这种消息在handler同步屏障文章中介绍过
             Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
             msg.arg1 = callbackType;
             msg.setAsynchronous(true);
             mHandler.sendMessageAtTime(msg, dueTime);
         }
     }
 }
private void scheduleFrameLocked(long now) {
     if (!mFrameScheduled) {
                mFrameScheduled = true;
        //如果使用垂直同步
         if (USE_VSYNC) {
                //判断是否运行在主线程,如果是则直接调用scheduleVsyncLocked()
                //如果运行在子线程则通过发送handler 的方式也会调用到scheduleVsyncLocked()
                if (isRunningOnLooperThreadLocked()) {//Looper.myLooper() == mLooper
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
         }else{
              final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
              
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
         }
     }
 }

 scheduleVsyncLocked()方法。

private void scheduleVsyncLocked() {
     //调用父类 DisplayEventReceiver的方法
     mDisplayEventReceiver.scheduleVsync();
}

 在scheduleVsync()方法中会调用nativeScheduleVsync,这是一个native方法,在native层执行完毕后会回调到java层的方法dispatchVsync()

scheduleVsync:向native层去请求一个Vsync信号。

dispatchVsync:请求到Vsync信号后,执行Java层的UI绘制和渲染逻辑。

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 { // 调用native 方法
        //调用Native方法请求一个Vsync信号,然后会从native层回调java层的dispatchVsync方法
        nativeScheduleVsync(mReceiverPtr);
    }
}

 timestampNanos:从Native层传递过来的一个时间戳,Vsync从native层发出的时间。

 // Called from native code.
 从native层回调java层的dispatchVsync方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
    onVsync(timestampNanos, physicalDisplayId, frame);
}

 在这又发送了一个异步消息,并且 Message.obtain(mHandler, this);第二个参数是一个callBack回调。所以没有handler的情况下,会执行这个回调函数。但是传的是this,所以就会执行this的run方法。这个this就是FrameDisplayEventReceiver的实例,在Choreographer的构造函数中初始化的。

 public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
      mTimestampNanos = timestampNanos;
        mFrame = frame;
        //得到message 添加了一个回调函数,this,则会调用run方法
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
 }

在FrameDisplayEventReceiver的run方法中,调用的doFrame方法

 @Override
 public void run() {
   mHavePendingVsync = false;
   doFrame(mTimestampNanos, mFrame);
 }
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }

            //sync信号发出的时间,
            long intendedFrameTimeNanos = frameTimeNanos;
            //当前的时间
            startNanos = System.nanoTime();
            //两者相减得到的时间差,就是底层消息通讯和回调所消耗的时间
            final long jitterNanos = startNanos - frameTimeNanos;
            //如果这个时间差大于了一帧的时间间隔。
            if (jitterNanos >= mFrameIntervalNanos) {
                //计算跳过了多少帧
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                //注意下面这行日子,如果跳帧大于30帧,系统会打印下面这行log,在主线程做了太多工作,会造成UI卡顿。
                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;
                }
                //用当前时间减去多出来的时间,就是下一帧要绘制的时间
                //进行绘制时间的修正,保证每一次的绘制时间间隔都是mFrameIntervalNanos
                frameTimeNanos = startNanos - lastFrameOffset;
            }
             //如果底层传过来的时间,小于上一帧绘制的时间,正常情况下,frameTimeNanos都是大于上一帧绘制的时间的。
            if (frameTimeNanos < mLastFrameTimeNanos) {

                //跳过本次的绘制,请求下一帧的时间
                scheduleVsyncLocked();
                return;
            }
            //以上的判断,都是为了控制绘制的频率。
            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            //重置标志位,可以再次进入scheduleFrameLocked
            mFrameScheduled = false;
            //将底层传过来的时间,记录为本次绘制的时间,也就是下一帧传过来时,上一帧绘制的时间。
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();

            //根据Choreographer的CallBack类型,进行callBack的回调。
            //输入
            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);
            //commit
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

这个是很重要的一个方法。

通过这个方法中的逻辑能够看出:Choreographer控制App层UI绘制的节奏和频率。

然后会按顺序执行一些列的doCallBacks函数。

首先会根据callbackType,从链表中取出CallBackRecord。然后再遍历CallBackRecord,调用他的run方法。

void doCallbacks(int callbackType, long frameTimeNanos) {
   CallbackRecord callbacks;
     synchronized (mLock) {
          final long now = System.nanoTime();
             //根据callbacktype,从链表中拿到 CallbackRecord
              callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                      now / TimeUtils.NANOS_PER_MS);
              if (callbacks == null) {
                  return;
              }
              mCallbacksRunning = true;

             for (CallbackRecord c = callbacks; c != null; c = c.next) {
                   //执行CallbackRecord的run方法
                    c.run(frameTimeNanos);
             }
     }

 }

 根据token来进行区分是FrameCallback类型还是Runnable。

主要这里的token传进来的是null,所以会执行else分支。

这个action就是 mTraversalRunnable,调用mTraversalRunnable的run方法。

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

public void run(long frameTimeNanos) {
     if (token == FRAME_CALLBACK_TOKEN) {
         ((FrameCallback)action).doFrame(frameTimeNanos);
     } else {
         ((Runnable)action).run();
     }
}
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

在它的run方法中执行了doTraversal()。

void doTraversal() {
     if (mTraversalScheduled) {
         mTraversalScheduled = false;
         //删除屏障消息
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

         //调用测量、布局和绘制方法
         performTraversals();

     }
 }
performTraversals()方法中就会调用
performMeasure、performLayout、performDraw,对View进行测量、布局、和绘制。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Choreographer::postCallbackDelayedInternal 是 Android 系统中一个非常重要的方法,用于将一个 Runnable 对象投递给 Choreographer ,以便在下一次 VSync 信号到来时执行该 Runnable 对象。该方法源码如下: ```java private void postCallbackDelayedInternal(int callbackType, Runnable action, Object token, long delayMillis) { synchronized (mLock) { // 如果 Choreographer 已经停止工作,则直接返回 if (mCallbacks == null) { return; } final long now = SystemClock.uptimeMillis(); final long when = now + delayMillis; // 将任务封装成 ChoreographerCallback 对象 final CallbackRecord callbacks = obtainCallbackLocked(callbackType, action, token, when); // 将 ChoreographerCallback 对象添加到任务队列中 addCallbackLocked(when, callbacks); // 如果任务队列中的任务数量超过了阈值,则向 Choreographer 发送消息 if (mFrameScheduled || (mFrameScheduled = mLooper.getQueue().isPolling())) { mTraversalScheduled = false; mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } else if (!mTraversalScheduled) { mTraversalScheduled = true; // 如果任务队列中的任务数量未超过阈值,则将提交遍历任务的操作延迟一段时间 mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null, FRAME_TIMEOUT_MS - delayMillis); } } } ``` 该方法主要有以下几个步骤: 1. 判断 Choreographer 是否已经停止工作,如果已经停止,则直接返回。 2. 将任务封装成 ChoreographerCallback 对象。 3. 将 ChoreographerCallback 对象添加到任务队列中。 4. 判断任务队列中的任务数量是否超过阈值,如果超过则向 Choreographer 发送消息,否则将提交遍历任务的操作延迟一段时间。 其中,步骤 2 和步骤 3 的实现比较简单,这里不再赘述,主要介绍一下步骤 4。 在 Android 系统中,Choreographer 是一个用于协调应用程序 UI 绘制和动画的系统组件,它的主要作用是通过 VSync 信号来同步应用程序的 UI 绘制和动画,保证帧率的稳定性和流畅性。Choreographer 在初始化时会创建一个 Handler 对象,并且在 Handler 中注册了一个消息回调函数,当 Handler 接收到消息时,就会执行该消息回调函数。 在 Choreographer 中,有两个回调函数,一个是 CALLBACK_TRAVERSAL,另一个是 CALLBACK_COMMIT。其中 CALLBACK_TRAVERSAL 用于执行应用程序的 UI 绘制操作,CALLBACK_COMMIT 用于执行应用程序的动画操作。Choreographer 会根据任务队列中的任务类型来决定将任务添加到哪个回调函数中。 在步骤 4 中,如果任务队列中的任务数量超过了阈值,Choreographer 就会向 Handler 发送 CALLBACK_TRAVERSAL 消息,并执行 CALLBACK_TRAVERSAL 回调函数中的任务。如果任务队列中的任务数量未超过阈值,Choreographer 就会将提交遍历任务的操作延迟一段时间,并延迟执行 CALLBACK_TRAVERSAL 回调函数中的任务。这样做的目的是为了尽量保证应用程序的帧率稳定性和流畅性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

niuyongzhi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值