am e19d5f02: Merge "Use choreographer frame time to schedule animations." into jb-dev

* commit 'e19d5f02728e34704a5e2c0fb9d7e54ef11aaa69':
  Use choreographer frame time to schedule animations.
This commit is contained in:
Jeff Brown
2012-04-27 16:47:52 -07:00
committed by Android Git Automerger
3 changed files with 111 additions and 75 deletions

View File

@@ -14,16 +14,6 @@ public class TimeAnimator extends ValueAnimator {
@Override @Override
boolean animationFrame(long currentTime) { boolean animationFrame(long currentTime) {
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = currentTime;
} else {
mStartTime = currentTime - mSeekTime;
// Now that we're playing, reset the seek time
mSeekTime = -1;
}
}
if (mListener != null) { if (mListener != null) {
long totalTime = currentTime - mStartTime; long totalTime = currentTime - mStartTime;
long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime); long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);

View File

@@ -55,11 +55,6 @@ public class ValueAnimator extends Animator {
*/ */
private static float sDurationScale = 1.0f; private static float sDurationScale = 1.0f;
/**
* Messages sent to timing handler: START is sent when an animation first begins.
*/
static final int ANIMATION_START = 0;
/** /**
* Values used with internal variable mPlayingState to indicate the current state of an * Values used with internal variable mPlayingState to indicate the current state of an
* animation. * animation.
@@ -504,7 +499,7 @@ public class ValueAnimator extends Animator {
mPlayingState = SEEKED; mPlayingState = SEEKED;
} }
mStartTime = currentTime - playTime; mStartTime = currentTime - playTime;
animationFrame(currentTime); doAnimationFrame(currentTime);
} }
/** /**
@@ -528,8 +523,9 @@ public class ValueAnimator extends Animator {
* the same times for calculating their values, which makes synchronizing * the same times for calculating their values, which makes synchronizing
* animations possible. * animations possible.
* *
* The handler uses the Choreographer for executing periodic callbacks.
*/ */
private static class AnimationHandler extends Handler implements Runnable { private static class AnimationHandler implements Runnable {
// The per-thread list of all active animations // The per-thread list of all active animations
private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
@@ -552,34 +548,13 @@ public class ValueAnimator extends Animator {
} }
/** /**
* The START message is sent when an animation's start() method is called. * Start animating on the next frame.
* It cannot start synchronously when start() is called
* because the call may be on the wrong thread, and it would also not be
* synchronized with other animations because it would not start on a common
* timing pulse. So each animation sends a START message to the handler, which
* causes the handler to place the animation on the active animations queue and
* start processing frames for that animation.
*/ */
@Override public void start() {
public void handleMessage(Message msg) { scheduleAnimation();
switch (msg.what) {
case ANIMATION_START:
// If there are already active animations, or if another ANIMATION_START
// message was processed during this frame, then the pending list may already
// have been cleared. If that's the case, we've already processed the
// active animations for this frame - don't do it again.
if (mPendingAnimations.size() > 0) {
doAnimationFrame();
}
break;
}
} }
private void doAnimationFrame() { private void doAnimationFrame(long frameTime) {
// currentTime holds the common time for all animations processed
// during this frame
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// mPendingAnimations holds any animations that have requested to be started // mPendingAnimations holds any animations that have requested to be started
// We're going to clear mPendingAnimations, but starting animation may // We're going to clear mPendingAnimations, but starting animation may
// cause more to be added to the pending list (for example, if one animation // cause more to be added to the pending list (for example, if one animation
@@ -605,7 +580,7 @@ public class ValueAnimator extends Animator {
int numDelayedAnims = mDelayedAnims.size(); int numDelayedAnims = mDelayedAnims.size();
for (int i = 0; i < numDelayedAnims; ++i) { for (int i = 0; i < numDelayedAnims; ++i) {
ValueAnimator anim = mDelayedAnims.get(i); ValueAnimator anim = mDelayedAnims.get(i);
if (anim.delayedAnimationFrame(currentTime)) { if (anim.delayedAnimationFrame(frameTime)) {
mReadyAnims.add(anim); mReadyAnims.add(anim);
} }
} }
@@ -626,7 +601,7 @@ public class ValueAnimator extends Animator {
int i = 0; int i = 0;
while (i < numAnims) { while (i < numAnims) {
ValueAnimator anim = mAnimations.get(i); ValueAnimator anim = mAnimations.get(i);
if (anim.animationFrame(currentTime)) { if (anim.doAnimationFrame(frameTime)) {
mEndingAnims.add(anim); mEndingAnims.add(anim);
} }
if (mAnimations.size() == numAnims) { if (mAnimations.size() == numAnims) {
@@ -652,10 +627,8 @@ public class ValueAnimator extends Animator {
// If there are still active or delayed animations, schedule a future call to // If there are still active or delayed animations, schedule a future call to
// onAnimate to process the next frame of the animations. // onAnimate to process the next frame of the animations.
if (!mAnimationScheduled if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
&& (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) { scheduleAnimation();
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
mAnimationScheduled = true;
} }
} }
@@ -663,7 +636,14 @@ public class ValueAnimator extends Animator {
@Override @Override
public void run() { public void run() {
mAnimationScheduled = false; mAnimationScheduled = false;
doAnimationFrame(); doAnimationFrame(mChoreographer.getFrameTime());
}
private void scheduleAnimation() {
if (!mAnimationScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
mAnimationScheduled = true;
}
} }
} }
@@ -935,7 +915,7 @@ public class ValueAnimator extends Animator {
mRunning = true; mRunning = true;
notifyStartListeners(); notifyStartListeners();
} }
animationHandler.sendEmptyMessage(ANIMATION_START); animationHandler.start();
} }
@Override @Override
@@ -1098,17 +1078,6 @@ public class ValueAnimator extends Animator {
*/ */
boolean animationFrame(long currentTime) { boolean animationFrame(long currentTime) {
boolean done = false; boolean done = false;
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = currentTime;
} else {
mStartTime = currentTime - mSeekTime;
// Now that we're playing, reset the seek time
mSeekTime = -1;
}
}
switch (mPlayingState) { switch (mPlayingState) {
case RUNNING: case RUNNING:
case SEEKED: case SEEKED:
@@ -1143,6 +1112,31 @@ public class ValueAnimator extends Animator {
return done; return done;
} }
/**
* Processes a frame of the animation, adjusting the start time if needed.
*
* @param frameTime The frame time.
* @return true if the animation has ended.
*/
final boolean doAnimationFrame(long frameTime) {
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = frameTime;
} else {
mStartTime = frameTime - mSeekTime;
// Now that we're playing, reset the seek time
mSeekTime = -1;
}
}
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
final long currentTime = Math.max(frameTime, mStartTime);
return animationFrame(currentTime);
}
/** /**
* Returns the current animation fraction, which is the elapsed/interpolated fraction used in * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
* the most recent frame update on the animation. * the most recent frame update on the animation.

View File

@@ -69,6 +69,12 @@ public final class Choreographer {
private static final boolean USE_VSYNC = SystemProperties.getBoolean( private static final boolean USE_VSYNC = SystemProperties.getBoolean(
"debug.choreographer.vsync", true); "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);
private static final long NANOS_PER_MS = 1000000;
private static final int MSG_DO_FRAME = 0; private static final int MSG_DO_FRAME = 0;
private static final int MSG_DO_SCHEDULE_VSYNC = 1; private static final int MSG_DO_SCHEDULE_VSYNC = 1;
private static final int MSG_DO_SCHEDULE_CALLBACK = 2; private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
@@ -84,7 +90,8 @@ public final class Choreographer {
private final CallbackQueue[] mCallbackQueues; private final CallbackQueue[] mCallbackQueues;
private boolean mFrameScheduled; private boolean mFrameScheduled;
private long mLastFrameTime; private boolean mCallbacksRunning;
private long mLastFrameTimeNanos;
/** /**
* Callback type: Input callback. Runs first. * Callback type: Input callback. Runs first.
@@ -108,7 +115,7 @@ public final class Choreographer {
mLooper = looper; mLooper = looper;
mHandler = new FrameHandler(looper); mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastFrameTime = Long.MIN_VALUE; mLastFrameTimeNanos = Long.MIN_VALUE;
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) { for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -270,6 +277,40 @@ 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.
*
* 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.
*/
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.
*/
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) { private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) { if (!mFrameScheduled) {
mFrameScheduled = true; mFrameScheduled = true;
@@ -289,7 +330,8 @@ public final class Choreographer {
mHandler.sendMessageAtFrontOfQueue(msg); mHandler.sendMessageAtFrontOfQueue(msg);
} }
} else { } else {
final long nextFrameTime = Math.max(mLastFrameTime + sFrameDelay, now); final long nextFrameTime = Math.max(
mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
} }
@@ -300,13 +342,18 @@ public final class Choreographer {
} }
} }
void doFrame(int frame) { void doFrame(long timestampNanos, int frame) {
synchronized (mLock) { synchronized (mLock) {
if (!mFrameScheduled) { if (!mFrameScheduled) {
return; // no work to do return; // no work to do
} }
mFrameScheduled = false; mFrameScheduled = false;
mLastFrameTime = SystemClock.uptimeMillis(); mLastFrameTimeNanos = timestampNanos;
}
final long startNanos;
if (DEBUG) {
startNanos = System.nanoTime();
} }
doCallbacks(Choreographer.CALLBACK_INPUT); doCallbacks(Choreographer.CALLBACK_INPUT);
@@ -314,20 +361,24 @@ public final class Choreographer {
doCallbacks(Choreographer.CALLBACK_TRAVERSAL); doCallbacks(Choreographer.CALLBACK_TRAVERSAL);
if (DEBUG) { if (DEBUG) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took " Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (SystemClock.uptimeMillis() - mLastFrameTime) + " ms."); + (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - timestampNanos) * 0.000001f + " ms.");
} }
} }
void doCallbacks(int callbackType) { void doCallbacks(int callbackType) {
final long start;
Callback callbacks; Callback callbacks;
synchronized (mLock) { synchronized (mLock) {
start = SystemClock.uptimeMillis(); final long now = SystemClock.uptimeMillis();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(start); callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
} }
try {
if (callbacks != null) {
for (Callback c = callbacks; c != null; c = c.next) { for (Callback c = callbacks; c != null; c = c.next) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "RunCallback: type=" + callbackType Log.d(TAG, "RunCallback: type=" + callbackType
@@ -336,8 +387,9 @@ public final class Choreographer {
} }
c.action.run(); c.action.run();
} }
} finally {
synchronized (mLock) { synchronized (mLock) {
mCallbacksRunning = false;
do { do {
final Callback next = callbacks.next; final Callback next = callbacks.next;
recycleCallbackLocked(callbacks); recycleCallbackLocked(callbacks);
@@ -404,7 +456,7 @@ public final class Choreographer {
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_DO_FRAME: case MSG_DO_FRAME:
doFrame(0); doFrame(System.nanoTime(), 0);
break; break;
case MSG_DO_SCHEDULE_VSYNC: case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync(); doScheduleVsync();
@@ -423,7 +475,7 @@ public final class Choreographer {
@Override @Override
public void onVsync(long timestampNanos, int frame) { public void onVsync(long timestampNanos, int frame) {
doFrame(frame); doFrame(timestampNanos, frame);
} }
} }