Choreographerpublic 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_INPUTCallback type: Input callback. Runs first. | public static final int | CALLBACK_ANIMATIONCallback type: Animation callback. Runs before traversals. | public static final int | CALLBACK_TRAVERSALCallback 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 |
---|
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 | 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 | doScheduleCallback(int callbackType)
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
| void | doScheduleVsync()
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
| void | dump(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 long | getFrameDelay()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 sFrameDelay;
| public long | getFrameIntervalNanos()
return mFrameIntervalNanos;
| public long | getFrameTime()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 getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
| public long | getFrameTimeNanos()Same as {@link #getFrameTime()} but with nanosecond precision.
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.Choreographer | getInstance()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 sThreadInstance.get();
| private static float | getRefreshRate()
DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
Display.DEFAULT_DISPLAY);
return di.refreshRate;
| private boolean | isRunningOnLooperThreadLocked()
return Looper.myLooper() == mLooper;
| private android.view.Choreographer$CallbackRecord | obtainCallbackLocked(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 void | postCallback(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.
postCallbackDelayed(callbackType, action, token, 0);
| public void | postCallbackDelayed(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.
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, 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 void | postFrameCallback(android.view.Choreographer$FrameCallback callback)Posts a frame callback to run on the next frame.
The callback runs once then is automatically removed.
postFrameCallbackDelayed(callback, 0);
| public void | postFrameCallbackDelayed(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.
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
| private void | recycleCallbackLocked(android.view.Choreographer$CallbackRecord callback)
callback.action = null;
callback.token = null;
callback.next = mCallbackPool;
mCallbackPool = callback;
| public void | removeCallbacks(int callbackType, java.lang.Runnable action, java.lang.Object token)Removes callbacks that have the specified action and token.
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
removeCallbacksInternal(callbackType, action, token);
| private void | removeCallbacksInternal(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 void | removeFrameCallback(android.view.Choreographer$FrameCallback callback)Removes a previously posted frame callback.
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
| 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 / 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 void | scheduleVsyncLocked()
mDisplayEventReceiver.scheduleVsync();
| public static void | setFrameDelay(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.
sFrameDelay = frameDelay;
| public static long | subtractFrameDelay(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.
final long frameDelay = sFrameDelay;
return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
|
|