summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/16.txt11
-rw-r--r--api/current.txt11
-rw-r--r--core/java/android/view/Choreographer.java297
3 files changed, 257 insertions, 62 deletions
diff --git a/api/16.txt b/api/16.txt
index 81efd34..0f5f3af 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -22737,6 +22737,17 @@ package android.view {
method public void onPrepareSubMenu(android.view.SubMenu);
}
+ public final class Choreographer {
+ method public static android.view.Choreographer getInstance();
+ method public void postFrameCallback(android.view.Choreographer.FrameCallback);
+ method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
+ method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
+ }
+
+ public static abstract interface Choreographer.FrameCallback {
+ method public abstract void doFrame(long);
+ }
+
public abstract interface CollapsibleActionView {
method public abstract void onActionViewCollapsed();
method public abstract void onActionViewExpanded();
diff --git a/api/current.txt b/api/current.txt
index 81efd34..0f5f3af 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22737,6 +22737,17 @@ package android.view {
method public void onPrepareSubMenu(android.view.SubMenu);
}
+ public final class Choreographer {
+ method public static android.view.Choreographer getInstance();
+ method public void postFrameCallback(android.view.Choreographer.FrameCallback);
+ method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long);
+ method public void removeFrameCallback(android.view.Choreographer.FrameCallback);
+ }
+
+ public static abstract interface Choreographer.FrameCallback {
+ method public abstract void doFrame(long);
+ }
+
public abstract interface CollapsibleActionView {
method public abstract void onActionViewCollapsed();
method public abstract void onActionViewExpanded();
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 183cb88..aaa081c 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -24,16 +24,46 @@ import android.os.SystemProperties;
import android.util.Log;
/**
- * Coordinates animations and drawing for UI on a particular thread.
- *
- * This object is thread-safe. Other threads can post callbacks to run at a later time
- * on the UI thread.
- *
- * Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver}
- * can only be accessed from the UI thread so operations that touch the event receiver
- * are posted to the UI thread if needed.
- *
- * @hide
+ * 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";
@@ -79,13 +109,22 @@ public final class Choreographer {
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 Callback mCallbackPool;
+ private CallbackRecord mCallbackPool;
private final CallbackQueue[] mCallbackQueues;
@@ -96,17 +135,20 @@ public final class Choreographer {
/**
* 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;
@@ -138,32 +180,38 @@ public final class Choreographer {
}
/**
- * 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
+ * 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. 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
+ * 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;
@@ -171,23 +219,25 @@ public final class Choreographer {
/**
* 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;
@@ -196,21 +246,26 @@ public final class Choreographer {
/**
* Posts a callback to run on the next frame.
- * The callback only runs once and then is automatically removed.
+ * <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 following the specified delay.
- * The callback only runs once and then is automatically removed.
+ * 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.
@@ -218,6 +273,7 @@ public final class Choreographer {
* @param delayMillis The delay time in milliseconds.
*
* @see #removeCallback
+ * @hide
*/
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
@@ -228,6 +284,11 @@ public final class Choreographer {
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
@@ -261,12 +322,17 @@ public final class Choreographer {
*
* @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);
@@ -281,17 +347,81 @@ public final class Choreographer {
}
/**
- * Gets the time when the current frame started. The frame time should be used
- * instead of {@link SystemClock#uptimeMillis()} to synchronize animations.
- * This 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 code actually ran.
+ * 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;
@@ -303,6 +433,7 @@ public final class Choreographer {
* @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) {
@@ -345,7 +476,7 @@ public final class Choreographer {
}
}
- void doFrame(long timestampNanos, int frame) {
+ void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
@@ -353,7 +484,7 @@ public final class Choreographer {
}
startNanos = System.nanoTime();
- final long jitterNanos = startNanos - timestampNanos;
+ final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG) {
@@ -363,10 +494,10 @@ public final class Choreographer {
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
}
- timestampNanos = startNanos - lastFrameOffset;
+ frameTimeNanos = startNanos - lastFrameOffset;
}
- if (timestampNanos < mLastFrameTimeNanos) {
+ 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");
@@ -376,24 +507,27 @@ public final class Choreographer {
}
mFrameScheduled = false;
- mLastFrameTimeNanos = timestampNanos;
+ mLastFrameTimeNanos = frameTimeNanos;
}
- doCallbacks(Choreographer.CALLBACK_INPUT);
- doCallbacks(Choreographer.CALLBACK_ANIMATION);
- doCallbacks(Choreographer.CALLBACK_TRAVERSAL);
+ 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 - timestampNanos) * 0.000001f + " ms.");
+ + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
- void doCallbacks(int callbackType) {
- Callback callbacks;
+ 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) {
@@ -402,19 +536,19 @@ public final class Choreographer {
mCallbacksRunning = true;
}
try {
- for (Callback c = callbacks; c != null; c = c.next) {
+ 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.action.run();
+ c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
- final Callback next = callbacks.next;
+ final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
@@ -449,10 +583,10 @@ public final class Choreographer {
return Looper.myLooper() == mLooper;
}
- private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) {
- Callback callback = mCallbackPool;
+ private CallbackRecord obtainCallbackLocked(long dueTime, Object action, Object token) {
+ CallbackRecord callback = mCallbackPool;
if (callback == null) {
- callback = new Callback();
+ callback = new CallbackRecord();
} else {
mCallbackPool = callback.next;
callback.next = null;
@@ -463,13 +597,44 @@ public final class Choreographer {
return callback;
}
- private void recycleCallbackLocked(Callback 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);
@@ -520,28 +685,36 @@ public final class Choreographer {
}
}
- private static final class Callback {
- public Callback next;
+ private static final class CallbackRecord {
+ public CallbackRecord next;
public long dueTime;
- public Runnable action;
+ 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 Callback mHead;
+ private CallbackRecord mHead;
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
- public Callback extractDueCallbacksLocked(long now) {
- Callback callbacks = mHead;
+ public CallbackRecord extractDueCallbacksLocked(long now) {
+ CallbackRecord callbacks = mHead;
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
- Callback last = callbacks;
- Callback next = last.next;
+ CallbackRecord last = callbacks;
+ CallbackRecord next = last.next;
while (next != null) {
if (next.dueTime > now) {
last.next = null;
@@ -554,9 +727,9 @@ public final class Choreographer {
return callbacks;
}
- public void addCallbackLocked(long dueTime, Runnable action, Object token) {
- Callback callback = obtainCallbackLocked(dueTime, action, token);
- Callback entry = mHead;
+ public void addCallbackLocked(long dueTime, Object action, Object token) {
+ CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
+ CallbackRecord entry = mHead;
if (entry == null) {
mHead = callback;
return;
@@ -576,10 +749,10 @@ public final class Choreographer {
entry.next = callback;
}
- public void removeCallbacksLocked(Runnable action, Object token) {
- Callback predecessor = null;
- for (Callback callback = mHead; callback != null;) {
- final Callback next = callback.next;
+ 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) {