亲宝软件园·资讯

展开

Android Choreographer

niuyongzhi 人气:0

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

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

加载全部内容

相关教程
猜你喜欢
用户评论