关闭

Choregographer工作原理分析

标签: androidui动画布局设计模式
427人阅读 评论(0) 收藏 举报
分类:

结论写在前面:Choreographer就是一个消息处理器,根据vsync 信号 来计算frame,而计算frame的方式就是处理三种回调,包括事件回调、动画回调、绘制回调。这三种事件在消息输入、加入动画、准备绘图layout 等动作时均会发给Choreographer。

下面来看分析过程

看过一些源码后,发现ui 绘制的管理,调度都是通过Choreographer这个类。

1 Choreographer 是什么?有什么?

Choreographer 是个普通类,final 表示不能被继承修改其行为。

  1. public final class Choreographer  
单例模式持有一个本地进程的单例对象,
  1. private static final ThreadLocal<Choreographer> sThreadInstance =  
  2.         new ThreadLocal<Choreographer>()  
该对象必须持有looper,意味着将使用消息队列
  1. protected Choreographer initialValue() {  
  2.     Looper looper = Looper.myLooper();  
  3.     if (looper == null) {  
  4.         throw new IllegalStateException("The current thread must have a looper!");  
  5.     }  
  6.     return new Choreographer(looper);  
  7. }  
持有一个handler 对象
  1. private final FrameHandler mHandler;  
这个handler对象仅处理3种事件:
  1. private final class FrameHandler extends Handler {  
  2.     public FrameHandler(Looper looper) {  
  3.         super(looper);  
  4.     }  
  5.   
  6.     @Override  
  7.     public void handleMessage(Message msg) {  
  8.         switch (msg.what) {  
  9.             case MSG_DO_FRAME:  
  10.                 doFrame(System.nanoTime(), 0);  
  11.                 break;  
  12.             case MSG_DO_SCHEDULE_VSYNC:  
  13.                 doScheduleVsync();  
  14.                 break;  
  15.             case MSG_DO_SCHEDULE_CALLBACK:  
  16.                 doScheduleCallback(msg.arg1);  
  17.                 break;  
  18.         }  
  19.     }  
  20. }  
在scheduleFrameLocked中事件被静态变量USE_VSYNC分开,也就是系统仅使用MSG_DO_SCHEDULE_VSYNC或MSG_DO_FRAME。

查一下概念,的确符合,DO_FRAME 是帧刷新,SCHEDULE_VSYNC是 垂直同步刷新。

在android4.1上加入的VSYNC 特性,并使用三重缓冲大幅改善了android 图像方面的性能。

这里有篇文章讲的很清楚:http://www.androidpolice.com/2012/07/12/getting-to-know-android-4-1-part-3-project-butter-how-it-works-and-what-it-added/


  1.     private void scheduleFrameLocked(long now) {  
  2.             if (USE_VSYNC) {  
  3.                 if (isRunningOnLooperThreadLocked()) {  
  4.                     scheduleVsyncLocked();  
  5.                 } else {  
  6.                     Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);  
  7.                     msg.setAsynchronous(true);  
  8.                     mHandler.sendMessageAtFrontOfQueue(msg);  
  9.                 }  
  10.             } else {  
  11.                 Message msg = mHandler.obtainMessage(MSG_DO_FRAME);  
  12.                 msg.setAsynchronous(true);  
  13.                 mHandler.sendMessageAtTime(msg, nextFrameTime);  
  14.             }  

2 从VSYNC 开始case MSG_DO_SCHEDULE_VSYNC:

  1. void doScheduleVsync() {  
  2.     synchronized (mLock) {  
  3.         if (mFrameScheduled) {  
  4.             scheduleVsyncLocked();  
  5.         }  
  6.     }  
  7. }  
  1. private void scheduleVsyncLocked() {  
  2.     mDisplayEventReceiver.scheduleVsync();  
  3. }  
  1. public void scheduleVsync() {  
  2.     if (mReceiverPtr == 0) {  
  3.         Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "  
  4.                 + "receiver has already been disposed.");  
  5.     } else {  
  6.         nativeScheduleVsync(mReceiverPtr);  
  7.     }  
  8. }  
一路调用到nativeScheduleVsync();

native的东西先不往下看,回头来看mDisplayEventReceiver

  1. private final class FrameDisplayEventReceiver extends DisplayEventReceiver  
  2.         implements Runnable   
观察一下方法名都是onXX ,一看就是回调的节奏
  1. // Called from native code.  
  2. @SuppressWarnings("unused")  
  3. private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {  
  4.     onVsync(timestampNanos, builtInDisplayId, frame);  
  5. }  
看到这里的注释,就明确了这个类就是被native回调了。回调时调用onVsync();而这个方法时在FrameDisplayEventReceiver中重写的。
  1.         @Override  
  2.         public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {  
  3.             mTimestampNanos = timestampNanos;  
  4.             mFrame = frame;  
  5.             Message msg = Message.obtain(mHandler, this);  
  6.             msg.setAsynchronous(true);  
  7.             mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS);  
  8.   
  9.         @Override  
  10.         public void run() {  
  11.             mHavePendingVsync = false;  
  12.             doFrame(mTimestampNanos, mFrame);  
  13.         }  
在onsync中把自己的runnable 加到消息队列中执行,这里使用异步消息,调用绘制do_frame()方法。
  1.     void doFrame(long frameTimeNanos, int frame) {  
  2.   
  3.         synchronized (mLock) {  
  4.             //省略一些赋值  
  5.             //可能时跳帧的情况,直接调用vsync,并return  
  6.             if (frameTimeNanos < mLastFrameTimeNanos) {  
  7.                 if (DEBUG) {  
  8.                     Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "  
  9.                             + "previously skipped frame.  Waiting for next vsync.");  
  10.                 }  
  11.                 scheduleVsyncLocked();  
  12.                 return;  
  13.             }  
  14.   
  15.         }  
  16.         //先后处理事件回调、动画回调、绘制回调  
  17.         doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);  
  18.         doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);  
  19.         doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);  
  20.     }  
在do_frame()中判断有跳帧可能性,直接继续vsync。

否则开始新的一帧的绘制,依次处理事件回调、动画回调、绘制回调

在doCallbacks() 中先根据当前时间去除队列中第一个有效的回调,然后依次处理这些回调。

  1.     void doCallbacks(int callbackType, long frameTimeNanos) {  
  2.         CallbackRecord callbacks;  
  3.         synchronized (mLock) {  
  4.             // We use "now" to determine when callbacks become due because it's possible  
  5.             // for earlier processing phases in a frame to post callbacks that should run  
  6.             // in a following phase, such as an input event that causes an animation to start.  
  7.             final long now = SystemClock.uptimeMillis();  
  8.             <span style="background-color: rgb(102, 255, 255);">//按时间取队列头</span>  
  9.             callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);  
  10.             if (callbacks == null) {  
  11.                 return;  
  12.             }  
  13.             mCallbacksRunning = true;  
  14.         }  
  15.         try {  
  16.             for (CallbackRecord c = callbacks; c != null; c = c.next) {  
  17.                 if (DEBUG) {  
  18.                     Log.d(TAG, "RunCallback: type=" + callbackType  
  19.                             + ", action=" + c.action + ", token=" + c.token  
  20.                             + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));  
  21.                 }  
  22.                 <span style="background-color: rgb(51, 255, 255);">//回调事件  
  23. </span>                c.run(frameTimeNanos);  
  24.             }  
  25.         } finally {  
  26.             synchronized (mLock) {  
  27.                 mCallbacksRunning = false;  
  28.                 do {  
  29.                     final CallbackRecord next = callbacks.next;  
  30.                     recycleCallbackLocked(callbacks);  
  31.                     callbacks = next;  
  32.                 } while (callbacks != null);  
  33.             }  
  34.         }  
  35.     }  

回调的数据结构是CallbackRecord,一个单向链表。根据token 的标志,来回调doframe 或者runnable
  1.     private static final class CallbackRecord {  
  2.         public CallbackRecord next;  
  3.         public long dueTime;  
  4.         public Object action; // Runnable or FrameCallback  
  5.         public Object token;  
  6.   
  7.         public void run(long frameTimeNanos) {  
  8.             if (token == FRAME_CALLBACK_TOKEN) {  
  9.                 ((FrameCallback)action).doFrame(frameTimeNanos);  
  10.             } else {  
  11.                 ((Runnable)action).run();  
  12.             }  
  13.         }  
  14.     }  
从这里可以看到,处理的事件分为两类:一类是doframe 事件,另一类是runnable 方法。接着,就来看一下到底是加的这些事件。

3. 哪里会使用Choreographer?

在《Android 动画animation 深入分析http://blog.csdn.net/farmer_cc/article/details/18259117中分析到scheduleAnimation 的时候就是调用的android.view.Choreographer.postCallback(int, Runnable, Object) 方法。查看该方法的调用,在UI 绘制、和其他和动画相关的类中均有调用。这里并没有全部列出来。

scheduleTraversals() : void - android.view.ViewRootImpl

scheduleAnimation() : void - android.animation.ValueAnimator.AnimationHandler

scheduleAnimationLocked() : void - com.android.server.wm.WindowManagerService

postOnAnimation(Runnable) : void - android.view.View


在postCallbackDelayedInternal()中 把新来的事件加入到队列中,并根据时间来判断是去frame 还是直接回调

  1.     private void postCallbackDelayedInternal(int callbackType,  
  2.             Object action, Object token, long delayMillis) {  
  3.         if (DEBUG) {  
  4.             Log.d(TAG, "PostCallback: type=" + callbackType  
  5.                     + ", action=" + action + ", token=" + token  
  6.                     + ", delayMillis=" + delayMillis);  
  7.         }  
  8.   
  9.         synchronized (mLock) {  
  10.             final long now = SystemClock.uptimeMillis();  
  11.             final long dueTime = now + delayMillis;  
  12.             <span style="background-color: rgb(51, 255, 255);">// 入队列</span>  
  13.             mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);  
  14.   
  15.             if (dueTime <= now) {  
  16.                 scheduleFrameLocked(now);  
  17.             } else {  
  18.                 <span style="background-color: rgb(51, 255, 255);">// 直接回调</span>  
  19.                 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);  
  20.                 msg.arg1 = callbackType;  
  21.                 msg.setAsynchronous(true);  
  22.                 mHandler.sendMessageAtTime(msg, dueTime);  
  23.             }  
  24.         }  

至此,事件的来龙去脉就明确了。Choreographer就是一个消息处理器,根据vsync 信号 来计算frame,而计算frame的方式就是处理三种回调,包括事件回调、动画回调、绘制回调。这三种事件在消息输入、加入动画、准备绘图layout 等动作时均会发给Choreographer。


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:96711次
    • 积分:1751
    • 等级:
    • 排名:千里之外
    • 原创:42篇
    • 转载:286篇
    • 译文:1篇
    • 评论:9条
    最新评论