屏幕刷新机制 Choreographer 原理分析
移动开发 Android
Android平台提供两种信号,一种是硬件信号,另一种是软件信号,由SurfaceFlinger进程的一个线程定时发出,硬件信号由硬件发出。

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

Android平台提供两种信号,一种是硬件信号,另一种是软件信号,由SurfaceFlinger进程的一个线程定时发出,硬件信号由硬件发出;

App进程若要通过gpu实现图像绘制,需要在接收到Vsync信号的条件下进行,因此,App进程访问SurfaceFlinger进程获取这个信号,再进行gpu绘制;

Android4.1之后增加了Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制时机;

Choreographer就是负责获取Vsync同步信号并控制App线程(主线程)完成图像绘制的类;

今天我们就来聊聊Choreographer机制;

一、Choreographer类介绍

1、实例初始化

  1. public ViewRootImpl(Context context, Display display) { 
  2.     ... 
  3.     //获取Choreographer实例 
  4.     mChoreographer = Choreographer.getInstance(); 
  5.     ... 
  6. public static Choreographer getInstance() { 
  7.     return sThreadInstance.get(); 
  8. private static final ThreadLocal<Choreographer> sThreadInstance = 
  9.         new ThreadLocal<Choreographer>() { 
  10.     @Override 
  11.     protected Choreographer initialValue() { 
  12.         Looper looper = Looper.myLooper(); 
  13.         if (looper == null) { 
  14.             throw new IllegalStateException("The current thread must have a looper!"); 
  15.         } 
  16.         return new Choreographer(looper); 
  17.     } 
  18. }; 
  • 每个线程中保存一个Choreographer实例对象;
  • 线程本地存储ThreadLocal变量,Choreographer类型,在主线程中初始化变量时,创建Choreographer对象,绑定主线程Looper;
  • 同一个App的每个窗体旗下ViewRootImpl使用的同一个Choregrapher对象,他控制者整个App中大部分视图的绘制节奏。

2、构造方法

  1. private Choreographer(Looper looper, int vsyncSource) { 
  2.         mLooper = looper; 
  3.         //使用当前线程looper创建 mHandler 
  4.         mHandler = new FrameHandler(looper); 
  5.         //USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver 
  6.         mDisplayEventReceiver = USE_VSYNC 
  7.                 ? new FrameDisplayEventReceiver(looper, vsyncSource) 
  8.                 : null
  9.         mLastFrameTimeNanos = Long.MIN_VALUE; 
  10.         // 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms 
  11.         mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); 
  12.         // 创建一个链表类型CallbackQueue的数组,大小为5, 
  13.         //也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL) 
  14.         mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; 
  15.         for (int i = 0; i <= CALLBACK_LAST; i++) { 
  16.             mCallbackQueues[i] = new CallbackQueue(); 
  17.         } 
  18.         // b/68769804: For low FPS experiments. 
  19.         setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1)); 
  20.     } 
  • 有一个Looper和一个FrameHandler变量;
  • 变量USE_VSYNC用于表示系统是否是用了Vsync同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的;
  • 系统使用了Vsync同步机制,创建一个FrameDisplayEventReceiver对象用于请求并接收Vsync事件;
  • Choreographer创建了一个大小为3的CallbackQueue队列数组,用于保存不同类型的Callback。

3、Callback类型

  1. //输入事件,首先执行 
  2.     public static final int CALLBACK_INPUT = 0; 
  3.     //动画,第二执行 
  4.     public static final int CALLBACK_ANIMATION = 1; 
  5.     //插入更新的动画,第三执行 
  6.     public static final int CALLBACK_INSETS_ANIMATION = 2; 
  7.     //绘制,第四执行 
  8.     public static final int CALLBACK_TRAVERSAL = 3; 
  9.     //提交,最后执行, 
  10.     public static final int CALLBACK_COMMIT = 4; 

五种类型任务对应存入对应的CallbackQueue中;

每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型。

4、FrameHandler处理的消息

  • CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的;
  • 而FrameHandler中主要处理三类消息:
  1. private final class FrameHandler extends Handler { 
  2.    public FrameHandler(Looper looper) { 
  3.        super(looper); 
  4.    } 
  5.    @Override 
  6.    public void handleMessage(Message msg) { 
  7.        switch (msg.what) { 
  8.            case MSG_DO_FRAME: 
  9.                doFrame(System.nanoTime(), 0); 
  10.                break; 
  11.            case MSG_DO_SCHEDULE_VSYNC: 
  12.                doScheduleVsync();   // 请求VSYNC信号 
  13.                break; 
  14.            case MSG_DO_SCHEDULE_CALLBACK: 
  15.                doScheduleCallback(msg.arg1); 
  16.                break; 
  17.        } 
  18.    } 
  • MSG_DO_FRAME 处理注册在Choreographer 的Runnable;
  • MSG_DO_SCHEDULE_VSYNC 直接请求下一帧的VSync信号;
  • MSG_DO_SCHEDULE_CALLBACK 根据Choreographer的配置执行合适的Handler延时处理;

二、Choreographer执行流程

1、requestLayout

  1. @Override 
  2.     public void requestLayout() { 
  3.         if (!mHandlingLayoutInLayoutRequest) { 
  4.             checkThread();//检查是否在当前线程 
  5.             mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。 
  6.             scheduleTraversals(); 
  7.         } 
  8.     } 
  9.     void scheduleTraversals() { 
  10.         if (!mTraversalScheduled) {//同一帧内不会多次调用遍历 
  11.             mTraversalScheduled = true
  12.             mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//拦截同步Message 
  13.             //Choreographer回调,执行绘制操作 
  14.             mChoreographer.postCallback( 
  15.                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  16.         } 
  17.     } 
  • postSyncBarrier : Handler 的同步屏障,它的作用是可以拦截 Looper 对同步消息的获取和分发,加入同步屏障之后,Looper 只会获取和处理异步消息,如果没有异步消息那么就会进入阻塞状态;
  • Choreographer: 编舞者,统一动画、输入和绘制时机;
  1. mChoreographer.postCallback( 
  2.                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  3. postCallback()->postCallbackDelayed()->postCallbackDelayedInternal(): 

2、postCallbackDelayedInternal

  1. private void postCallbackDelayedInternal(int callbackType, 
  2.        Object action, Object token, long delayMillis) { 
  3.    synchronized (mLock) { 
  4.        // 当前时间 
  5.        final long now = SystemClock.uptimeMillis(); 
  6.        // 回调执行时间,为当前时间加上延迟的时间 
  7.        final long dueTime = now + delayMillis; 
  8.        // obtainCallbackLocked(long dueTime, Object action, Object token)会将传入的3个参数转换为CallbackRecord(具体请看源码,非主要部分,此处略过),然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。 
  9.        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
  10.        if (dueTime <= now) { 
  11.            // 如果delayMillis=0的话,dueTime=now,则会马上执行 
  12.            scheduleFrameLocked(now); 
  13.        } else { 
  14.            // 如果dueTime>now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法 
  15.            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 
  16.            msg.arg1 = callbackType; 
  17.            msg.setAsynchronous(true); 
  18.            mHandler.sendMessageAtTime(msg, dueTime); 
  19.        } 
  20.    } 
  • mCallbackQueues先把对应的callback添加到链表上来,然后判断是否有延迟;
  • 如果没有则会马上执行scheduleFrameLocked,如果有,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理;
  • 其最终处理也是执行scheduleFrameLocked(long now)方法;

3、scheduleFrameLocked

  1. private void scheduleFrameLocked(long now) { 
  2.    if (!mFrameScheduled) { 
  3.        mFrameScheduled = true
  4.        if (USE_VSYNC) { 
  5.            // 如果使用了VSYNC,由系统值确定 
  6.            if (DEBUG_FRAMES) { 
  7.                Log.d(TAG, "Scheduling next frame on vsync."); 
  8.            } 
  9.            if (isRunningOnLooperThreadLocked()) { 
  10.                // 请求VSYNC信号,最终会调到Native层,Native处理完成后触发FrameDisplayEventReceiver的onVsync回调,回调中最后也会调用doFrame(long frameTimeNanos, int frame)方法 
  11.                scheduleVsyncLocked(); 
  12.            } else { 
  13.                // 在UI线程上直接发送一个what=MSG_DO_SCHEDULE_VSYNC的消息,最终也会调到scheduleVsyncLocked()去请求VSYNC信号 
  14.                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); 
  15.                msg.setAsynchronous(true); 
  16.                mHandler.sendMessageAtFrontOfQueue(msg); 
  17.            } 
  18.        } else { 
  19.            // 没有使用VSYNC 
  20.            final long nextFrameTime = Math.max
  21.                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); 
  22.            if (DEBUG_FRAMES) { 
  23.                Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); 
  24.            } 
  25.            // 直接发送一个what=MSG_DO_FRAME的消息,消息处理时调用doFrame(long frameTimeNanos, int frame)方法 
  26.            Message msg = mHandler.obtainMessage(MSG_DO_FRAME); 
  27.            msg.setAsynchronous(true); 
  28.            mHandler.sendMessageAtTime(msg, nextFrameTime); 
  29.        } 
  30.    } 
  1. // Enable/disable vsync for animations and drawing. 
  2.     private static final boolean USE_VSYNC = SystemProperties.getBoolean( 
  3.             "debug.choreographer.vsync"true); 

常量USE_VSYNC,表示是否允许动画和绘制的垂直同步,默认是为true;

判断USE_VSYNC,如果使用了VSYNC:走scheduleVsyncLocked,即请求VSYNC信号,最终调用doFrame;

如果没使用VSYNC,则通过消息执行doFrame;

4、scheduleVsyncLocked

请求VSYNC信号的流程;

  1. private void scheduleVsyncLocked() { 
  2.     mDisplayEventReceiver.scheduleVsync(); 
  3. public void scheduleVsync() { 
  4.     if (mReceiverPtr == 0) { 
  5.         Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " 
  6.                 + "receiver has already been disposed."); 
  7.     } else { 
  8.         nativeScheduleVsync(mReceiverPtr); 
  9.     } 
  • mDisplayEventReceiver 对应的是FrameDisplayEventReceiver,它继承自 DisplayEventReceiver , 主要是用来接收同步脉冲信号 VSYNC;
  • scheduleVsync()方法通过底层nativeScheduleVsync()向SurfaceFlinger 服务注册,即在下一次脉冲接收后会调用 DisplayEventReceiver的dispatchVsync()方法;
  • 这里类似于订阅者模式,但是每次调用nativeScheduleVsync()方法都有且只有一次dispatchVsync()方法回调;
  • 底层向应用层发送VSYNC信号,java层通过dispatchVsync()接收,最后回调在FrameDisplayEventReceiver的onVsync;

5、FrameDisplayEventReceiver

  1. private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { 
  2.     private boolean mHavePendingVsync; 
  3.     private long mTimestampNanos; 
  4.     private int mFrame; 
  5.     @Override 
  6.     public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { 
  7.         //忽略来自第二显示屏的Vsync 
  8.         if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) { 
  9.             scheduleVsync(); 
  10.             return
  11.         } 
  12.         ... 
  13.         mTimestampNanos = timestampNanos; 
  14.         mFrame = frame; 
  15.         //该消息的callback为当前对象FrameDisplayEventReceiver 
  16.         Message msg = Message.obtain(mHandler, this); 
  17.         msg.setAsynchronous(true); 
  18.         //此处mHandler为FrameHandler 
  19.         mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); 
  20.     } 
  21.     @Override 
  22.     public void run() { 
  23.         mHavePendingVsync = false
  24.         doFrame(mTimestampNanos, mFrame);  
  25.     } 
  • onVsync()过程是通过FrameHandler向主线程Looper发送了一个自带callback的消息 callback为FrameDisplayEventReceiver;
  • 当主线程Looper执行到该消息时,则调用FrameDisplayEventReceiver.run()方法,紧接着便是调用doFrame;

6、doFrame

  1. void doFrame(long frameTimeNanos, int frame) { 
  2.         final long startNanos; 
  3.         synchronized (mLock) { 
  4.             ... 
  5.             //是否有跳帧,如果有那么就打印log并且修正偏差 
  6.         } 
  7.         //执行callback 
  8.         try { 
  9.             Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); 
  10.             AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); 
  11.             mFrameInfo.markInputHandlingStart(); 
  12.             doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); 
  13.             mFrameInfo.markAnimationsStart(); 
  14.             doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); 
  15.             mFrameInfo.markPerformTraversalsStart(); 
  16.             doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); 
  17.             doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); 
  18.         } finally { 
  19.             AnimationUtils.unlockAnimationClock(); 
  20.             Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
  21.         } 
  22.         if (DEBUG_FRAMES) { 
  23.             final long endNanos = System.nanoTime(); 
  24.             Log.d(TAG, "Frame " + frame + ": Finished, took " 
  25.                     + (endNanos - startNanos) * 0.000001f + " ms, latency " 
  26.                     + (startNanos - frameTimeNanos) * 0.000001f + " ms."); 
  27.         } 
  28.     } 

doFrame方法做的就是渲染下一帧,检测是否卡顿并修补卡顿,然后开始做渲染工作,doCallbacks方法的参数:

  • CALLBACK_INPUT:输入;
  • CALLBACK_ANIMATION:动画;
  • CALLBACK_TRAVERSAL:遍历,执行measure、layout、draw;
  • CALLBACK_COMMIT:遍历完成的提交操作,用来修正动画启动时间;

7、doCallbacks

  1. void doCallbacks(int callbackType, long frameTimeNanos) { 
  2.     CallbackRecord callbacks; 
  3.     synchronized (mLock) { 
  4.         final long now = SystemClock.uptimeMillis(); 
  5.         //从指定类型的CallbackQueue队列中查找执行时间到的CallbackRecord 
  6.         callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now); 
  7.         if (callbacks == null) { 
  8.             return
  9.         } 
  10.         mCallbacksRunning = true
  11.     } 
  12.     try { 
  13.         //由于CallbackQueues是按时间先后顺序排序的,因此遍历执行所有时间到的CallbackRecord 
  14.         for (CallbackRecord c = callbacks; c != null; c = c.next) { 
  15.             c.run(frameTimeNanos); 
  16.         } 
  17.     } finally { 
  18.         synchronized (mLock) { 
  19.             mCallbacksRunning = false
  20.             do { 
  21.                 final CallbackRecord next = callbacks.next
  22.                 recycleCallbackLocked(callbacks); 
  23.                 callbacks = next
  24.             } while (callbacks != null); 
  25.         } 
  26.     } 

Choreographer内部维护了这四种链表,渲染每一帧的时候都会从上往下的去执行相应的渲染操作,有输入那么就先渲染输入队列,有动画就渲染动画,然后遍历,然后提交;

8、Choreographer总结

  • 控制外部输入事件处理,动画执行,UI变化,以及提交执行都是在同一个类中做的处理,即是Choreographer;
  • Choreographer支持4种类型事件:输入、绘制、动画、提交,并通过postCallback在对应需要同步vsync进行刷新处进行注册,等待回调;
  • 每次执行的时候,Choreographer会根据当前的时间,只处理事件链表中最后一个事件,当有耗时操作在主线程时,事件不能及时执行,就会出现所谓的“跳帧”,“卡顿”现象;
  • Choreographer的共有方法postCallback(callbackType, Object)是往事件链表中放事件的方法,而doFrame()是消耗这些事件的方法;
  • Choreographer监听底层Vsync信号,一旦接收到回调信号,则通过doFrame统一对java层4种类型事件进行回调。

 

责任编辑:武晓燕 来源: Android开发编程

同话题下的热门内容

Android版Twitter客户端终于迎来社区功能Google 在 Windows 平台开启 Android 游戏测试,率先登录港台地区

编辑推荐

史上最详细的Toolbar开发讲解,此篇必读!Android仿京东、天猫app的商品详情页的布局架构, 以及功能实现一篇文章玩转ButterKnife,让代码更简洁从安卓设备转移数据到iOS的几种方法,掌握一种你就可以换机无忧爱奇艺CTO汤兴:道天地将法,《孙子兵法》的管理之道
我收藏的内容
点赞
收藏

51CTO技术栈视频号