/frameworks/base/core/java/android/view/Choreographer.java
Java | 822 lines | 445 code | 80 blank | 297 comment | 87 complexity | 8743a836ec6b8a46363faa5a92cc4761 MD5 | raw file
- /*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.view;
- import android.hardware.display.DisplayManagerGlobal;
- import android.os.Handler;
- import android.os.Looper;
- import android.os.Message;
- import android.os.SystemClock;
- import android.os.SystemProperties;
- import android.util.Log;
- /**
- * Coordinates the timing of animations, input and drawing.
- * <p>
- * The choreographer receives timing pulses (such as vertical synchronization)
- * from the display subsystem then schedules work to occur as part of rendering
- * the next display frame.
- * </p><p>
- * Applications typically interact with the choreographer indirectly using
- * higher level abstractions in the animation framework or the view hierarchy.
- * Here are some examples of things you can do using the higher-level APIs.
- * </p>
- * <ul>
- * <li>To post an animation to be processed on a regular time basis synchronized with
- * display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
- * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
- * frame, use {@link View#postOnAnimation}.</li>
- * <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
- * frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
- * <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
- * next display frame, use {@link View#postInvalidateOnAnimation()} or
- * {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
- * <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
- * sync with display frame rendering, do nothing. This already happens automatically.
- * {@link View#onDraw} will be called at the appropriate time.</li>
- * </ul>
- * <p>
- * However, there are a few cases where you might want to use the functions of the
- * choreographer directly in your application. Here are some examples.
- * </p>
- * <ul>
- * <li>If your application does its rendering in a different thread, possibly using GL,
- * or does not use the animation framework or view hierarchy at all
- * and you want to ensure that it is appropriately synchronized with the display, then use
- * {@link Choreographer#postFrameCallback}.</li>
- * <li>... and that's about it.</li>
- * </ul>
- * <p>
- * Each {@link Looper} thread has its own choreographer. Other threads can
- * post callbacks to run on the choreographer but they will run on the {@link Looper}
- * to which the choreographer belongs.
- * </p>
- */
- public final class Choreographer {
- private static final String TAG = "Choreographer";
- private static final boolean DEBUG = false;
- // The default amount of time in ms between animation frames.
- // When vsync is not enabled, we want to have some idea of how long we should
- // wait before posting the next animation message. It is important that the
- // default value be less than the true inter-frame delay on all devices to avoid
- // situations where we might skip frames by waiting too long (we must compensate
- // for jitter and hardware variations). Regardless of this value, the animation
- // and display loop is ultimately rate-limited by how fast new graphics buffers can
- // be dequeued.
- private static final long DEFAULT_FRAME_DELAY = 10;
- // The number of milliseconds between animation frames.
- private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY;
- // Thread local storage for the choreographer.
- private static final ThreadLocal<Choreographer> sThreadInstance =
- new ThreadLocal<Choreographer>() {
- @Override
- protected Choreographer initialValue() {
- Looper looper = Looper.myLooper();
- if (looper == null) {
- throw new IllegalStateException("The current thread must have a looper!");
- }
- return new Choreographer(looper);
- }
- };
- // Enable/disable vsync for animations and drawing.
- private static final boolean USE_VSYNC = SystemProperties.getBoolean(
- "debug.choreographer.vsync", true);
- // Enable/disable using the frame time instead of returning now.
- private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
- "debug.choreographer.frametime", true);
- // Set a limit to warn about skipped frames.
- // Skipped frames imply jank.
- private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
- "debug.choreographer.skipwarning", 30);
- private static final long NANOS_PER_MS = 1000000;
- private static final int MSG_DO_FRAME = 0;
- private static final int MSG_DO_SCHEDULE_VSYNC = 1;
- private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
- // All frame callbacks posted by applications have this token.
- private static final Object FRAME_CALLBACK_TOKEN = new Object() {
- public String toString() { return "FRAME_CALLBACK_TOKEN"; }
- };
- private final Object mLock = new Object();
- private final Looper mLooper;
- private final FrameHandler mHandler;
- // The display event receiver can only be accessed by the looper thread to which
- // it is attached. We take care to ensure that we post message to the looper
- // if appropriate when interacting with the display event receiver.
- private final FrameDisplayEventReceiver mDisplayEventReceiver;
- private CallbackRecord mCallbackPool;
- private final CallbackQueue[] mCallbackQueues;
- private boolean mFrameScheduled;
- private boolean mCallbacksRunning;
- private long mLastFrameTimeNanos;
- private long mFrameIntervalNanos;
- /**
- * Callback type: Input callback. Runs first.
- * @hide
- */
- public static final int CALLBACK_INPUT = 0;
- /**
- * Callback type: Animation callback. Runs before traversals.
- * @hide
- */
- public static final int CALLBACK_ANIMATION = 1;
- /**
- * Callback type: Traversal callback. Handles layout and draw. Runs last
- * after all other asynchronous messages have been handled.
- * @hide
- */
- public static final int CALLBACK_TRAVERSAL = 2;
- private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
- private Choreographer(Looper looper) {
- mLooper = looper;
- mHandler = new FrameHandler(looper);
- mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : 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();
- }
- }
- private static float getRefreshRate() {
- DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
- Display.DEFAULT_DISPLAY);
- return di.refreshRate;
- }
- /**
- * Gets the choreographer for the calling thread. Must be called from
- * a thread that already has a {@link android.os.Looper} associated with it.
- *
- * @return The choreographer for this thread.
- * @throws IllegalStateException if the thread does not have a looper.
- */
- public static Choreographer getInstance() {
- return sThreadInstance.get();
- }
- /**
- * The amount of time, in milliseconds, between each frame of the animation.
- * <p>
- * This is a requested time that the animation will attempt to honor, but the actual delay
- * between frames may be different, depending on system load and capabilities. This is a static
- * function because the same delay will be applied to all animations, since they are all
- * run off of a single timing loop.
- * </p><p>
- * The frame delay may be ignored when the animation system uses an external timing
- * source, such as the display refresh rate (vsync), to govern animations.
- * </p>
- *
- * @return the requested time between frames, in milliseconds
- * @hide
- */
- public static long getFrameDelay() {
- return sFrameDelay;
- }
- /**
- * The amount of time, in milliseconds, between each frame of the animation.
- * <p>
- * This is a requested time that the animation will attempt to honor, but the actual delay
- * between frames may be different, depending on system load and capabilities. This is a static
- * function because the same delay will be applied to all animations, since they are all
- * run off of a single timing loop.
- * </p><p>
- * The frame delay may be ignored when the animation system uses an external timing
- * source, such as the display refresh rate (vsync), to govern animations.
- * </p>
- *
- * @param frameDelay the requested time between frames, in milliseconds
- * @hide
- */
- public static void setFrameDelay(long frameDelay) {
- sFrameDelay = frameDelay;
- }
- /**
- * Subtracts typical frame delay time from a delay interval in milliseconds.
- * <p>
- * This method can be used to compensate for animation delay times that have baked
- * in assumptions about the frame delay. For example, it's quite common for code to
- * assume a 60Hz frame time and bake in a 16ms delay. When we call
- * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
- * posting the animation callback but let the animation timer take care of the remaining
- * frame delay time.
- * </p><p>
- * This method is somewhat conservative about how much of the frame delay it
- * subtracts. It uses the same value returned by {@link #getFrameDelay} which by
- * default is 10ms even though many parts of the system assume 16ms. Consequently,
- * we might still wait 6ms before posting an animation callback that we want to run
- * on the next frame, but this is much better than waiting a whole 16ms and likely
- * missing the deadline.
- * </p>
- *
- * @param delayMillis The original delay time including an assumed frame delay.
- * @return The adjusted delay time with the assumed frame delay subtracted out.
- * @hide
- */
- public static long subtractFrameDelay(long delayMillis) {
- final long frameDelay = sFrameDelay;
- return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
- }
- /**
- * Posts a callback to run on the next frame.
- * <p>
- * The callback runs once then is automatically removed.
- * </p>
- *
- * @param callbackType The callback type.
- * @param action The callback action to run during the next frame.
- * @param token The callback token, or null if none.
- *
- * @see #removeCallbacks
- * @hide
- */
- public void postCallback(int callbackType, Runnable action, Object token) {
- postCallbackDelayed(callbackType, action, token, 0);
- }
- /**
- * Posts a callback to run on the next frame after the specified delay.
- * <p>
- * The callback runs once then is automatically removed.
- * </p>
- *
- * @param callbackType The callback type.
- * @param action The callback action to run during the next frame after the specified delay.
- * @param token The callback token, or null if none.
- * @param delayMillis The delay time in milliseconds.
- *
- * @see #removeCallback
- * @hide
- */
- public void postCallbackDelayed(int callbackType,
- Runnable action, Object token, long delayMillis) {
- if (action == null) {
- throw new IllegalArgumentException("action must not be null");
- }
- if (callbackType < 0 || callbackType > CALLBACK_LAST) {
- throw new IllegalArgumentException("callbackType is invalid");
- }
- postCallbackDelayedInternal(callbackType, action, token, delayMillis);
- }
- 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);
- }
- }
- }
- /**
- * Removes callbacks that have the specified action and token.
- *
- * @param callbackType The callback type.
- * @param action The action property of the callbacks to remove, or null to remove
- * callbacks with any action.
- * @param token The token property of the callbacks to remove, or null to remove
- * callbacks with any token.
- *
- * @see #postCallback
- * @see #postCallbackDelayed
- * @hide
- */
- public void removeCallbacks(int callbackType, Runnable action, Object token) {
- if (callbackType < 0 || callbackType > CALLBACK_LAST) {
- throw new IllegalArgumentException("callbackType is invalid");
- }
- removeCallbacksInternal(callbackType, action, token);
- }
- private void removeCallbacksInternal(int callbackType, Object action, Object token) {
- if (DEBUG) {
- Log.d(TAG, "RemoveCallbacks: type=" + callbackType
- + ", action=" + action + ", token=" + token);
- }
- synchronized (mLock) {
- mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
- if (action != null && token == null) {
- mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
- }
- }
- }
- /**
- * Posts a frame callback to run on the next frame.
- * <p>
- * The callback runs once then is automatically removed.
- * </p>
- *
- * @param callback The frame callback to run during the next frame.
- *
- * @see #postFrameCallbackDelayed
- * @see #removeFrameCallback
- */
- public void postFrameCallback(FrameCallback callback) {
- postFrameCallbackDelayed(callback, 0);
- }
- /**
- * Posts a frame callback to run on the next frame after the specified delay.
- * <p>
- * The callback runs once then is automatically removed.
- * </p>
- *
- * @param callback The frame callback to run during the next frame.
- * @param delayMillis The delay time in milliseconds.
- *
- * @see #postFrameCallback
- * @see #removeFrameCallback
- */
- public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- postCallbackDelayedInternal(CALLBACK_ANIMATION,
- callback, FRAME_CALLBACK_TOKEN, delayMillis);
- }
- /**
- * Removes a previously posted frame callback.
- *
- * @param callback The frame callback to remove.
- *
- * @see #postFrameCallback
- * @see #postFrameCallbackDelayed
- */
- public void removeFrameCallback(FrameCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
- }
- /**
- * Gets the time when the current frame started.
- * <p>
- * This method provides the time in nanoseconds when the frame started being rendered.
- * The frame time provides a stable time base for synchronizing animations
- * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
- * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
- * time helps to reduce inter-frame jitter because the frame time is fixed at the time
- * the frame was scheduled to start, regardless of when the animations or drawing
- * callback actually runs. All callbacks that run as part of rendering a frame will
- * observe the same frame time so using the frame time also helps to synchronize effects
- * that are performed by different callbacks.
- * </p><p>
- * Please note that the framework already takes care to process animations and
- * drawing using the frame time as a stable time base. Most applications should
- * not need to use the frame time information directly.
- * </p><p>
- * This method should only be called from within a callback.
- * </p>
- *
- * @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
- *
- * @throws IllegalStateException if no frame is in progress.
- * @hide
- */
- public long getFrameTime() {
- return getFrameTimeNanos() / NANOS_PER_MS;
- }
- /**
- * Same as {@link #getFrameTime()} but with nanosecond precision.
- *
- * @return The frame start time, in the {@link System#nanoTime()} time base.
- *
- * @throws IllegalStateException if no frame is in progress.
- * @hide
- */
- public long getFrameTimeNanos() {
- synchronized (mLock) {
- if (!mCallbacksRunning) {
- throw new IllegalStateException("This method must only be called as "
- + "part of a callback while a frame is in progress.");
- }
- return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
- }
- }
- private void scheduleFrameLocked(long now) {
- if (!mFrameScheduled) {
- mFrameScheduled = true;
- if (USE_VSYNC) {
- if (DEBUG) {
- Log.d(TAG, "Scheduling next frame on vsync.");
- }
- // If running on the Looper thread, then schedule the vsync immediately,
- // otherwise post a message to schedule the vsync from the UI thread
- // as soon as possible.
- if (isRunningOnLooperThreadLocked()) {
- scheduleVsyncLocked();
- } else {
- Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtFrontOfQueue(msg);
- }
- } else {
- final long nextFrameTime = Math.max(
- mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
- if (DEBUG) {
- Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
- }
- Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, nextFrameTime);
- }
- }
- }
- void doFrame(long frameTimeNanos, int frame) {
- final long startNanos;
- synchronized (mLock) {
- if (!mFrameScheduled) {
- return; // no work to do
- }
- startNanos = System.nanoTime();
- final long jitterNanos = startNanos - frameTimeNanos;
- if (jitterNanos >= mFrameIntervalNanos) {
- final long skippedFrames = jitterNanos / mFrameIntervalNanos;
- if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
- Log.i(TAG, "Skipped " + skippedFrames + " frames! "
- + "The application may be doing too much work on its main thread.");
- }
- final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
- if (DEBUG) {
- Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
- + "which is more than the frame interval of "
- + (mFrameIntervalNanos * 0.000001f) + " ms! "
- + "Skipping " + skippedFrames + " frames and setting frame "
- + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
- }
- frameTimeNanos = startNanos - lastFrameOffset;
- }
- 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;
- }
- mFrameScheduled = false;
- mLastFrameTimeNanos = frameTimeNanos;
- }
- doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
- doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
- doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
- if (DEBUG) {
- final long endNanos = System.nanoTime();
- Log.d(TAG, "Frame " + frame + ": Finished, took "
- + (endNanos - startNanos) * 0.000001f + " ms, latency "
- + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
- }
- }
- 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);
- }
- }
- }
- void doScheduleVsync() {
- synchronized (mLock) {
- if (mFrameScheduled) {
- scheduleVsyncLocked();
- }
- }
- }
- void doScheduleCallback(int callbackType) {
- synchronized (mLock) {
- if (!mFrameScheduled) {
- final long now = SystemClock.uptimeMillis();
- if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
- scheduleFrameLocked(now);
- }
- }
- }
- }
- private void scheduleVsyncLocked() {
- mDisplayEventReceiver.scheduleVsync();
- }
- private boolean isRunningOnLooperThreadLocked() {
- return Looper.myLooper() == mLooper;
- }
- private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
- CallbackRecord callback = mCallbackPool;
- if (callback == null) {
- callback = new CallbackRecord();
- } else {
- mCallbackPool = callback.next;
- callback.next = null;
- }
- callback.dueTime = dueTime;
- callback.action = action;
- callback.token = token;
- return callback;
- }
- private void recycleCallbackLocked(CallbackRecord callback) {
- callback.action = null;
- callback.token = null;
- callback.next = mCallbackPool;
- mCallbackPool = callback;
- }
- /**
- * Implement this interface to receive a callback when a new display frame is
- * being rendered. The callback is invoked on the {@link Looper} thread to
- * which the {@link Choreographer} is attached.
- */
- public interface FrameCallback {
- /**
- * Called when a new display frame is being rendered.
- * <p>
- * This method provides the time in nanoseconds when the frame started being rendered.
- * The frame time provides a stable time base for synchronizing animations
- * and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
- * or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
- * time helps to reduce inter-frame jitter because the frame time is fixed at the time
- * the frame was scheduled to start, regardless of when the animations or drawing
- * callback actually runs. All callbacks that run as part of rendering a frame will
- * observe the same frame time so using the frame time also helps to synchronize effects
- * that are performed by different callbacks.
- * </p><p>
- * Please note that the framework already takes care to process animations and
- * drawing using the frame time as a stable time base. Most applications should
- * not need to use the frame time information directly.
- * </p>
- *
- * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
- * in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
- * to convert it to the {@link SystemClock#uptimeMillis()} time base.
- */
- public void doFrame(long frameTimeNanos);
- }
- 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;
- }
- }
- }
- private final class FrameDisplayEventReceiver extends DisplayEventReceiver
- implements Runnable {
- private boolean mHavePendingVsync;
- private long mTimestampNanos;
- private int mFrame;
- public FrameDisplayEventReceiver(Looper looper) {
- super(looper);
- }
- @Override
- public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
- // Ignore vsync from secondary display.
- // This can be problematic because the call to scheduleVsync() is a one-shot.
- // We need to ensure that we will still receive the vsync from the primary
- // display which is the one we really care about. Ideally we should schedule
- // vsync for a particular display.
- // At this time Surface Flinger won't send us vsyncs for secondary displays
- // but that could change in the future so let's log a message to help us remember
- // that we need to fix this.
- if (builtInDisplayId != Surface.BUILT_IN_DISPLAY_ID_MAIN) {
- Log.d(TAG, "Received vsync from secondary display, but we don't support "
- + "this case yet. Choreographer needs a way to explicitly request "
- + "vsync for a specific display to ensure it doesn't lose track "
- + "of its scheduled vsync.");
- scheduleVsync();
- return;
- }
- // Post the vsync event to the Handler.
- // The idea is to prevent incoming vsync events from completely starving
- // the message queue. If there are no messages in the queue with timestamps
- // earlier than the frame time, then the vsync event will be processed immediately.
- // Otherwise, messages that predate the vsync event will be handled first.
- long now = System.nanoTime();
- if (timestampNanos > now) {
- Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
- + " ms in the future! Check that graphics HAL is generating vsync "
- + "timestamps using the correct timebase.");
- timestampNanos = now;
- }
- if (mHavePendingVsync) {
- Log.w(TAG, "Already have a pending vsync event. There should only be "
- + "one at a time.");
- } else {
- mHavePendingVsync = true;
- }
- 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);
- }
- }
- 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();
- }
- }
- }
- private final class CallbackQueue {
- private CallbackRecord mHead;
- public boolean hasDueCallbacksLocked(long now) {
- return mHead != null && mHead.dueTime <= now;
- }
- public CallbackRecord extractDueCallbacksLocked(long now) {
- CallbackRecord callbacks = mHead;
- if (callbacks == null || callbacks.dueTime > now) {
- return null;
- }
- CallbackRecord last = callbacks;
- CallbackRecord next = last.next;
- while (next != null) {
- if (next.dueTime > now) {
- last.next = null;
- break;
- }
- last = next;
- next = next.next;
- }
- mHead = next;
- return callbacks;
- }
- public void addCallbackLocked(long dueTime, Object action, Object token) {
- CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
- CallbackRecord entry = mHead;
- if (entry == null) {
- mHead = callback;
- return;
- }
- if (dueTime < entry.dueTime) {
- callback.next = entry;
- mHead = callback;
- return;
- }
- while (entry.next != null) {
- if (dueTime < entry.next.dueTime) {
- callback.next = entry.next;
- break;
- }
- entry = entry.next;
- }
- entry.next = callback;
- }
- public void removeCallbacksLocked(Object action, Object token) {
- CallbackRecord predecessor = null;
- for (CallbackRecord callback = mHead; callback != null;) {
- final CallbackRecord next = callback.next;
- if ((action == null || callback.action == action)
- && (token == null || callback.token == token)) {
- if (predecessor != null) {
- predecessor.next = next;
- } else {
- mHead = next;
- }
- recycleCallbackLocked(callback);
- } else {
- predecessor = callback;
- }
- callback = next;
- }
- }
- }
- }