深入分析UI 上层事件处理核心机制 Choreographer

深入分析UI 上层事件处理核心机制 Choreographer

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

下面来看分析过程

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

1 Choreographer 是什么?有什么?

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

public final class Choreographer
单例模式持有一个本地进程的单例对象,

    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>()
该对象必须持有looper,意味着将使用消息队列

        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            return new Choreographer(looper);
        }
持有一个handler 对象

    private final FrameHandler mHandler;
这个handler对象仅处理3种事件:

    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;
            }
        }
    }
在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/


    private void scheduleFrameLocked(long now) {
            if (USE_VSYNC) {
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }

2 从VSYNC 开始case MSG_DO_SCHEDULE_VSYNC:

    void doScheduleVsync() {
        synchronized (mLock) {
            if (mFrameScheduled) {
                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);
        }
    }
一路调用到nativeScheduleVsync();

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

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable 
观察一下方法名都是onXX ,一看就是回调的节奏

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        onVsync(timestampNanos, builtInDisplayId, frame);
    }
看到这里的注释,就明确了这个类就是被native回调了。回调时调用onVsync();而这个方法时在FrameDisplayEventReceiver中重写的。

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / NANOS_PER_MS);

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
在onsync中把自己的runnable 加到消息队列中执行,这里使用异步消息,调用绘制do_frame()方法。

    void doFrame(long frameTimeNanos, int frame) {

        synchronized (mLock) {
            //省略一些赋值
            //可能时跳帧的情况,直接调用vsync,并return
            if (frameTimeNanos < mLastFrameTimeNanos) {
                if (DEBUG) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                scheduleVsyncLocked();
                return;
            }

        }
        //先后处理事件回调、动画回调、绘制回调
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    }
在do_frame()中判断有跳帧可能性,直接继续vsync。

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

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

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
            // in a following phase, such as an input event that causes an animation to start.
            final long now = SystemClock.uptimeMillis();
            //按时间取队列头
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;
        }
        try {
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                //回调事件
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
        }
    }

回调的数据结构是CallbackRecord,一个单向链表。根据token 的标志,来回调doframe 或者runnable
    private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }
从这里可以看到,处理的事件分为两类:一类是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 还是直接回调

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG) {
            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);
            }
        }

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


写在后面:结合前文《Android 动画animation 深入分析http://blog.csdn.net/farmer_cc/article/details/18259117中可以更多了解动画相关内容

  • 8
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Choreographer::postCallbackDelayedInternal 是 Android 系统中一个非常重要的方法,用于将一个 Runnable 对象投递给 Choreographer ,以便在下一次 VSync 信号到来时执行该 Runnable 对象。该方法源码如下: ```java private void postCallbackDelayedInternal(int callbackType, Runnable action, Object token, long delayMillis) { synchronized (mLock) { // 如果 Choreographer 已经停止工作,则直接返回 if (mCallbacks == null) { return; } final long now = SystemClock.uptimeMillis(); final long when = now + delayMillis; // 将任务封装成 ChoreographerCallback 对象 final CallbackRecord callbacks = obtainCallbackLocked(callbackType, action, token, when); // 将 ChoreographerCallback 对象添加到任务队列中 addCallbackLocked(when, callbacks); // 如果任务队列中的任务数量超过了阈值,则向 Choreographer 发送消息 if (mFrameScheduled || (mFrameScheduled = mLooper.getQueue().isPolling())) { mTraversalScheduled = false; mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } else if (!mTraversalScheduled) { mTraversalScheduled = true; // 如果任务队列中的任务数量未超过阈值,则将提交遍历任务的操作延迟一段时间 mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null, FRAME_TIMEOUT_MS - delayMillis); } } } ``` 该方法主要有以下几个步骤: 1. 判断 Choreographer 是否已经停止工作,如果已经停止,则直接返回。 2. 将任务封装成 ChoreographerCallback 对象。 3. 将 ChoreographerCallback 对象添加到任务队列中。 4. 判断任务队列中的任务数量是否超过阈值,如果超过则向 Choreographer 发送消息,否则将提交遍历任务的操作延迟一段时间。 其中,步骤 2 和步骤 3 的实现比较简单,这里不再赘述,主要介绍一下步骤 4。 在 Android 系统中,Choreographer 是一个用于协调应用程序 UI 绘制和动画的系统组件,它的主要作用是通过 VSync 信号来同步应用程序的 UI 绘制和动画,保证帧率的稳定性和流畅性。Choreographer 在初始化时会创建一个 Handler 对象,并且在 Handler 中注册了一个消息回调函数,当 Handler 接收到消息时,就会执行该消息回调函数。 在 Choreographer 中,有两个回调函数,一个是 CALLBACK_TRAVERSAL,另一个是 CALLBACK_COMMIT。其中 CALLBACK_TRAVERSAL 用于执行应用程序的 UI 绘制操作,CALLBACK_COMMIT 用于执行应用程序的动画操作。Choreographer 会根据任务队列中的任务类型来决定将任务添加到哪个回调函数中。 在步骤 4 中,如果任务队列中的任务数量超过了阈值,Choreographer 就会向 Handler 发送 CALLBACK_TRAVERSAL 消息,并执行 CALLBACK_TRAVERSAL 回调函数中的任务。如果任务队列中的任务数量未超过阈值,Choreographer 就会将提交遍历任务的操作延迟一段时间,并延迟执行 CALLBACK_TRAVERSAL 回调函数中的任务。这样做的目的是为了尽量保证应用程序的帧率稳定性和流畅性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值