您的位置:首页 > 产品设计 > UI/UE

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

2014-01-22 00:13 609 查看

深入分析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中可以更多了解动画相关内容
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: