Android Choreographer源码详细分析

 更新时间:2022年08月26日 14:04:28   作者:niuyongzhi  
Choreographer的作用主要是配合Vsync,给上层App的渲染提供一个稳定的Message处理的时机,也就是Vsync到来的时候,系统通过对Vsync信号周期的调整,来控制每一帧绘制操作的时机

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

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进行测量、布局、和绘制。

到此这篇关于Android Choreographer源码详细分析的文章就介绍到这了,更多相关Android Choreographer内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论