2021年1月6日星期三

Choreographer全解析

前言

今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。

代码未动,图先行

UI变化

上期说到app并不是每一个vsync信号都能接收到的,只有当应用有绘制需求的时候,才会通过scheduledVsync 方法申请VSYNC信号。

那我们就从有绘制需求开始看,当我们修改了UI后,都会执行invalidate方法进行绘制,这里我们举例setText方法,再回顾下修改UI时候的流程:

可以看到,最后会调用到父布局ViewRootImplscheduleTraversals方法。

 public ViewRootImpl(Context context, Display display) {  //...  mChoreographer = Choreographer.getInstance(); } void scheduleTraversals() {  if (!mTraversalScheduled) {   mTraversalScheduled = true;   mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();   mChoreographer.postCallback(     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);   //...  } }

为了方便查看,我只留了相关代码。可以看到,在ViewRootImpl构造方法中,实例化了Choreographer对象,并且在发现UI变化的时候调用的scheduleTraversals方法中,调用了postSyncBarrier方法插入了同步屏障,然后调用了postCallback方法,并且传入了一个mTraversalRunnable(后面有用处,先留意一下),暂时还不知道这个方法是干嘛的。继续看看。

Choreographer实例化

//Choreographer.java public static Choreographer getInstance() {  return sThreadInstance.get(); }  private static final ThreadLocal<Choreographer> sThreadInstance =   new ThreadLocal<Choreographer>() {  @Override  protected Choreographer initialValue() {   Looper looper = Looper.myLooper();   //...   Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);   //...   return choreographer;  } };   private Choreographer(Looper looper, int vsyncSource) {  mLooper = looper;  mHandler = new FrameHandler(looper);    //初始化FrameDisplayEventReceiver  mDisplayEventReceiver = USE_VSYNC    ? new FrameDisplayEventReceiver(looper, vsyncSource)    : null;  mLastFrameTimeNanos = Long.MIN_VALUE;				//一帧间隔时间  mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());  mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];  for (int i = 0; i <= CALLBACK_LAST; i++) {   mCallbackQueues[i] = new CallbackQueue();  } } 

ThreadLocal,是不是有点熟悉?之前说Handler的时候说过,Handler是怎么获取当前线程的Looper的?就是通过这个ThreadLocal,同样,这里也是用到ThreadLocal来保证每个线程对应一个Choreographer

存储方法还是一样,以ThreadLocal为key,Choreographer为value存储到ThreadLocalMap中,不熟悉的朋友可以再翻到《Handler另类难点三问》看看。

所以这里创建的mHandler就是ViewRootImpl所处的线程的handler。接着看postCallback做了什么。

postCallback

 private void postCallbackDelayedInternal(int callbackType,   Object action, Object token, long delayMillis) {  if (DEBUG_FRAMES) {   Log.d(TAG, "PostCallback: type=" + callbackType     + ", action=" + action + ", token=" + token     + ", delayMillis=" + delayMillis);  }  synchronized (mLock) {   final long now = SystemClock.uptimeMillis();   final long dueTime = now + delayMillis;   mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);   if (dueTime <= now) {    scheduleFrameLocked(now);   } else {    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);    msg.arg1 = callbackType;    msg.setAsynchronous(true);    mHandler.sendMessageAtTime(msg, dueTime);   }  } }   private 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;    case MSG_DO_SCHEDULE_VSYNC:     doScheduleVsync();     break;    case MSG_DO_SCHEDULE_CALLBACK:     doScheduleCallback(msg.arg1);     break;   }  } }   void doScheduleCallback(int callbackType) {  synchronized (mLock) {   if (!mFrameScheduled) {    final long now = SystemClock.uptimeMillis();    if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {     scheduleFrameLocked(now);    }   }  } } 

ViewRootImpl中调用了postCallback方法之后,可以看到通过addCallbackLocked方法,添加了一条CallbackRecord数据,其中action就是对应之前ViewRootImplmTraversalRunnable

然后判断设定的时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息消息MSG_DO_SCHEDULE_CALLBACK到Handler所在线程,并最终执行到scheduleFrameLocked方法。如果没有延迟,则直接执行scheduleFrameLocked

scheduleFrameLocked(准备申请VSYNC信号)

 private void scheduleFrameLocked(long now) {  if (!mFrameScheduled) {   mFrameScheduled = true;   if (USE_VSYNC) {    //是否运行在主线程    if (isRunningOnLooperThreadLocked()) {     scheduleVsyncLocked();    } else {     //通过Handler切换线程     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);   }  } } case MSG_DO_FRAME:   doFrame(System.nanoTime(), 0);   break; case MSG_DO_SCHEDULE_VSYNC:   doScheduleVsync();   break;    void doScheduleVsync() {  synchronized (mLock) {   if (mFrameScheduled) {    scheduleVsyncLocked();   }  } }     

该方法中,首先判断了是否开启了VSYNC(上节说过Android4.1之后默认开启VSYNC),如果开启了,判断在不在主线程,如果是主线程就运行scheduleVsyncLocked,如果不在就切换线程,也会调用到scheduleVsyncLocked方法,而这个方法就是我们之前说过的申请VSYNC信号的方法了。

如果没有开启VSYNC,则直接调用doFrame方法。

另外可以看到,刚才我们用到Handler发送消息的时候,都调用了msg.setAsynchronous(true)方法,这个方法就是设置消息为异步消息。因为我们刚才一开始的时候设置了同步屏障,所以异步消息就会先执行,这里的设置异步也就是为了让消息第一时间执行而不受其他Handler消息影响。

小结1

通过上面一系列方法,我们能得到一个初步的逻辑过程了:

  • ViewRootImpl初始化的时候,会实例化Choreographer对象,也就是获取当前线程(一般就是主线程)对应的Choreographer对象。
  • Choreographer初始化的时候,会新建一个当前线程对应的Handler对象,初始化FrameDisplayEventReceiver,计算一帧的时间等一系列初始化工作。
  • 当UI改变的时候,会调用到ViewRootImplscheduleTraversals方法,这个方法中加入了同步屏障消息,并且调用了Choreographer的postCallback方法去申请VSYNC信号。

在这个过程中,Handler发送了延迟消息,切换了线程,并且给消息都设置了异步,保证最先执行。

继续看scheduleVsyncLocked方法。

scheduleVsyncLocked

 private void scheduleVsyncLocked() {  mDisplayEventReceiver.scheduleVsync(); }  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 {   nativeScheduleVsync(mReceiverPtr);  } }

代码很简单,就是通过FrameDisplayEventReceiver,请求native层面的垂直同步信号VSYNC。

这个FrameDisplayEventReceiver是在Choreographer构造方法中实例化的,继承自DisplayEventReceiver,主要就是处理VSYNC信号的申请和接收。

刚才说到调用nativeScheduleVsync方法申请VSYNC信号,然后当收到VSYNC信号的时候就会回调onVsync方法了。

onVsync(接收VSYNC信号)

 private final class FrameDisplayEventReceiver extends DisplayEventReceiver   implements Runnable {    @Override  public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {      //...   mTimestampNanos = timestampNanos;   mFrame = frame;   Message msg = Message.obtain(mHandler, this);   msg.setAsynchronous(true);   mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);  }  @Override  public void run() {   mHavePendingVsync = false;   doFrame(mTimestampNanos, mFrame);  } }

这里同样通过Handler发送了一条消息,执行了本身的Runnable回调方法,也就是doFrame()

doFrame(绘制帧数据)

 void doFrame(long frameTimeNanos, int frame) {  final long startNanos;  synchronized (mLock) {   //...      //当前帧的vsync信号来的时间,假如为12分200ms   long intendedFrameTimeNanos = frameTimeNanos;   //当前时间,也就是开始绘制的时间,假如为12分150ms   startNanos = System.nanoTime();   //计算时间差,如果大于一个帧时间,则是跳帧了。比如是50ms,大于16ms   final long jitterNanos = startNanos - frameTimeNanos;   if (jitterNanos >= mFrameIntervalNanos) {    //计算掉了几帧,50/16=3帧    final long skippedFrames = jitterNanos / mFrameIntervalNanos;    //计算一帧内时间差,50%16=2ms    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;    //修正时间,vsync信号应该来得时间,为12分148ms,保证和绘制时间对应上    frameTimeNanos = startNanos - lastFrameOffset;   }      if (frameTimeNanos < mLastFrameTimeNanos) {    //信号时间已过,不能再绘制了,等待下一个vsync信号,保证后续时间同步上    scheduleVsyncLocked();    return;   }      mFrameScheduled = false;   mLastFrameTimeNanos = frameTimeNanos;  }  try {      //执行相关的callback任务   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);  } }

这里主要的工作就是:

  • 设置当前帧的开始绘制时间,上节说过开始绘制要在vsync信号来的时候开始,保证两者时间对应。所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上。
  • 执行所有的Callback任务。

doCallbacks(执行任务)

 void doCallbacks(int callbackType, long frameTimeNanos) {  CallbackRecord callbacks;  synchronized (mLock) {      final long now = System.nanoTime();   callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(     now / TimeUtils.NANOS_PER_MS);   if (callbacks == null) {    return;   }   mCallbacksRunning = true;   if (callbackType == Choreographer.CALLBACK_COMMIT) {    final long jitterNanos = now - frameTimeNanos;    Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);    if (jitterNanos >= 2 * mFrameIntervalNanos) {     final long lastFrameOffset = jitterNanos % mFrameIntervalNanos       + mFrameIntervalNanos;          frameTimeNanos = now - lastFrameOffset;     mLastFrameTimeNanos = frameTimeNanos;    }   }  }  try {   Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);   for (CallbackRecord c = callbacks; c != null; c = c.next) {    c.run(frameTimeNanos);   }  } finally {   synchronized (mLock) {    mCallbacksRunning = false;    do {     final CallbackRecord next = callbacks.next;     recycleCallbackLocked(callbacks);     callbacks = next;    } while (callbacks != null);   }   Trace.traceEnd(Trace.TRACE_TAG_VIEW);  } }  private static final class CallbackRecord {  public CallbackRecord next;  public long dueTime;  public Object action; // Runnable or FrameCallback  public Object token;  @UnsupportedAppUsage  public void run(long frameTimeNanos) {   if (token == FRAME_CALLBACK_TOKEN) {    ((FrameCallback)action).doFrame(frameTimeNanos);   } else {    ((Runnable)action).run();   }  } } 

其实就是按类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法。

而这个run方法中,判断了token,并执行了action的对应方法。再回头看看我们当初ViewRootImpl传入的方法:

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

token为空,所以会执行到action也就是mTraversalRunnable的run方法。

所以兜兜转转,又回到了ViewRootImpl本身,通过Choreographer申请了VSYNC信号,然后接收了VSYNC信号,最终回到自己这里,开始view的绘制。

最后看看mTraversalRunnable的run方法。

mTraversalRunnable

 final class TraversalRunnable implements Runnable {  @Override  public void run() {   doTraversal();  } }  void doTraversal() {  if (mTraversalScheduled) {   mTraversalScheduled = false;   mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);   if (mProfile) {    Debug.startMethodTracing("ViewAncestor");   }   performTraversals();   if (mProfile) {    Debug.stopMethodTracing();    mProfile = false;   }  } } 

这就很熟悉了吧,调用了performTraversals方法,也就是开始了测量,布局,绘制的步骤。同时,关闭了同步屏障。

总结

最后再看看总结图:

参考

https://juejin.cn/post/6863756420380196877
https://blog.csdn.net/stven_king/article/details/78775166

拜拜

有一起学习的小伙伴可以关注下❤️ 我的公众号——码上积木,每天剖析一个知识点,我们一起积累知识。公众号回复111可获得面试题《思考与解答》以往期刊。









原文转载:http://www.shaoqun.com/a/506011.html

跨境电商:https://www.ikjzd.com/

mil:https://www.ikjzd.com/w/1285

auditor:https://www.ikjzd.com/w/2437


前言今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。代码未动,图先行UI变化上期说到app并不是每一个vsync信号都能接收到的,只有当应用有绘制需求的时候,才会通过scheduledVsync方法申请VSYNC信号。那我们就从有绘制需求开始看,当我们修改了U
一淘网:一淘网
netporter:netporter
凤凰朝阳宫怎样?:凤凰朝阳宫怎样?
不来春熙路 你就不算到过了成都 - :不来春熙路 你就不算到过了成都 -
2019年电子商务数据统计,全球电商发展趋势都在这!:2019年电子商务数据统计,全球电商发展趋势都在这!

没有评论:

发表评论