FileDocCategorySizeDatePackage
Choreographer.javaAPI DocAndroid 5.1 API33483Thu Mar 12 22:22:10 GMT 2015android.view

Choreographer

public final class Choreographer extends Object
Coordinates the timing of animations, input and drawing.

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.

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.

  • To post an animation to be processed on a regular time basis synchronized with display frame rendering, use {@link android.animation.ValueAnimator#start}.
  • To post a {@link Runnable} to be invoked once at the beginning of the next display frame, use {@link View#postOnAnimation}.
  • To post a {@link Runnable} to be invoked once at the beginning of the next display frame after a delay, use {@link View#postOnAnimationDelayed}.
  • 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)}.
  • 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.

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.

  • 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}.
  • ... and that's about it.

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.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final long
DEFAULT_FRAME_DELAY
private static volatile long
sFrameDelay
private static final ThreadLocal
sThreadInstance
private static final boolean
USE_VSYNC
private static final boolean
USE_FRAME_TIME
private static final int
SKIPPED_FRAME_WARNING_LIMIT
private static final int
MSG_DO_FRAME
private static final int
MSG_DO_SCHEDULE_VSYNC
private static final int
MSG_DO_SCHEDULE_CALLBACK
private static final Object
FRAME_CALLBACK_TOKEN
private final Object
mLock
private final android.os.Looper
mLooper
private final FrameHandler
mHandler
private final FrameDisplayEventReceiver
mDisplayEventReceiver
private CallbackRecord
mCallbackPool
private final CallbackQueue[]
mCallbackQueues
private boolean
mFrameScheduled
private boolean
mCallbacksRunning
private long
mLastFrameTimeNanos
private long
mFrameIntervalNanos
public static final int
CALLBACK_INPUT
Callback type: Input callback. Runs first.
public static final int
CALLBACK_ANIMATION
Callback type: Animation callback. Runs before traversals.
public static final int
CALLBACK_TRAVERSAL
Callback type: Traversal callback. Handles layout and draw. Runs last after all other asynchronous messages have been handled.
private static final int
CALLBACK_LAST
Constructors Summary
private Choreographer(android.os.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();
        }
    
Methods Summary
voiddoCallbacks(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);
            }
        }
    
voiddoFrame(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.");
        }
    
voiddoScheduleCallback(int callbackType)

        synchronized (mLock) {
            if (!mFrameScheduled) {
                final long now = SystemClock.uptimeMillis();
                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                    scheduleFrameLocked(now);
                }
            }
        }
    
voiddoScheduleVsync()

        synchronized (mLock) {
            if (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
    
voiddump(java.lang.String prefix, java.io.PrintWriter writer)

        String innerPrefix = prefix + "  ";
        writer.print(prefix); writer.println("Choreographer:");
        writer.print(innerPrefix); writer.print("mFrameScheduled=");
                writer.println(mFrameScheduled);
        writer.print(innerPrefix); writer.print("mLastFrameTime=");
                writer.println(TimeUtils.formatUptime(mLastFrameTimeNanos / 1000000));
    
public static longgetFrameDelay()
The amount of time, in milliseconds, between each frame of the animation.

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.

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.

return
the requested time between frames, in milliseconds
hide

        return sFrameDelay;
    
public longgetFrameIntervalNanos()

return
The refresh rate as the nanoseconds between frames
hide

        return mFrameIntervalNanos;
    
public longgetFrameTime()
Gets the time when the current frame started.

This method provides the time in milliseconds 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.

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.

This method should only be called from within a callback.

return
The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
throws
IllegalStateException if no frame is in progress.
hide

        return getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
    
public longgetFrameTimeNanos()
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

        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();
        }
    
public static android.view.ChoreographergetInstance()
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.

        return sThreadInstance.get();
    
private static floatgetRefreshRate()

        DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
                Display.DEFAULT_DISPLAY);
        return di.refreshRate;
    
private booleanisRunningOnLooperThreadLocked()

        return Looper.myLooper() == mLooper;
    
private android.view.Choreographer$CallbackRecordobtainCallbackLocked(long dueTime, java.lang.Object action, java.lang.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;
    
public voidpostCallback(int callbackType, java.lang.Runnable action, java.lang.Object token)
Posts a callback to run on the next frame.

The callback runs once then is automatically removed.

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

        postCallbackDelayed(callbackType, action, token, 0);
    
public voidpostCallbackDelayed(int callbackType, java.lang.Runnable action, java.lang.Object token, long delayMillis)
Posts a callback to run on the next frame after the specified delay.

The callback runs once then is automatically removed.

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

        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 voidpostCallbackDelayedInternal(int callbackType, java.lang.Object action, java.lang.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);
            }
        }
    
public voidpostFrameCallback(android.view.Choreographer$FrameCallback callback)
Posts a frame callback to run on the next frame.

The callback runs once then is automatically removed.

param
callback The frame callback to run during the next frame.
see
#postFrameCallbackDelayed
see
#removeFrameCallback

        postFrameCallbackDelayed(callback, 0);
    
public voidpostFrameCallbackDelayed(android.view.Choreographer$FrameCallback callback, long delayMillis)
Posts a frame callback to run on the next frame after the specified delay.

The callback runs once then is automatically removed.

param
callback The frame callback to run during the next frame.
param
delayMillis The delay time in milliseconds.
see
#postFrameCallback
see
#removeFrameCallback

        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        postCallbackDelayedInternal(CALLBACK_ANIMATION,
                callback, FRAME_CALLBACK_TOKEN, delayMillis);
    
private voidrecycleCallbackLocked(android.view.Choreographer$CallbackRecord callback)

        callback.action = null;
        callback.token = null;
        callback.next = mCallbackPool;
        mCallbackPool = callback;
    
public voidremoveCallbacks(int callbackType, java.lang.Runnable action, java.lang.Object token)
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

        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        removeCallbacksInternal(callbackType, action, token);
    
private voidremoveCallbacksInternal(int callbackType, java.lang.Object action, java.lang.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);
            }
        }
    
public voidremoveFrameCallback(android.view.Choreographer$FrameCallback callback)
Removes a previously posted frame callback.

param
callback The frame callback to remove.
see
#postFrameCallback
see
#postFrameCallbackDelayed

        if (callback == null) {
            throw new IllegalArgumentException("callback must not be null");
        }

        removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
    
private voidscheduleFrameLocked(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 / TimeUtils.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);
            }
        }
    
private voidscheduleVsyncLocked()

        mDisplayEventReceiver.scheduleVsync();
    
public static voidsetFrameDelay(long frameDelay)
The amount of time, in milliseconds, between each frame of the animation.

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.

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.

param
frameDelay the requested time between frames, in milliseconds
hide

        sFrameDelay = frameDelay;
    
public static longsubtractFrameDelay(long delayMillis)
Subtracts typical frame delay time from a delay interval in milliseconds.

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.

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.

param
delayMillis The original delay time including an assumed frame delay.
return
The adjusted delay time with the assumed frame delay subtracted out.
hide

        final long frameDelay = sFrameDelay;
        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;