diff options
-rw-r--r-- | core/java/android/animation/TimeAnimator.java | 10 | ||||
-rwxr-xr-x | core/java/android/animation/ValueAnimator.java | 96 | ||||
-rw-r--r-- | core/java/android/view/Choreographer.java | 80 |
3 files changed, 111 insertions, 75 deletions
diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java index a79f2a3..088d20d 100644 --- a/core/java/android/animation/TimeAnimator.java +++ b/core/java/android/animation/TimeAnimator.java @@ -14,16 +14,6 @@ public class TimeAnimator extends ValueAnimator { @Override 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) { long totalTime = currentTime - mStartTime; long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime); diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 2154b14..326f27c 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -56,11 +56,6 @@ public class ValueAnimator extends Animator { 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 * animation. */ @@ -504,7 +499,7 @@ public class ValueAnimator extends Animator { mPlayingState = SEEKED; } 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 * 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 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. - * 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. + * Start animating on the next frame. */ - @Override - public void handleMessage(Message msg) { - 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; - } + public void start() { + scheduleAnimation(); } - private void doAnimationFrame() { - // currentTime holds the common time for all animations processed - // during this frame - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - + private void doAnimationFrame(long frameTime) { // mPendingAnimations holds any animations that have requested to be started // We're going to clear mPendingAnimations, but starting animation may // 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(); for (int i = 0; i < numDelayedAnims; ++i) { ValueAnimator anim = mDelayedAnims.get(i); - if (anim.delayedAnimationFrame(currentTime)) { + if (anim.delayedAnimationFrame(frameTime)) { mReadyAnims.add(anim); } } @@ -626,7 +601,7 @@ public class ValueAnimator extends Animator { int i = 0; while (i < numAnims) { ValueAnimator anim = mAnimations.get(i); - if (anim.animationFrame(currentTime)) { + if (anim.doAnimationFrame(frameTime)) { mEndingAnims.add(anim); } 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 // onAnimate to process the next frame of the animations. - if (!mAnimationScheduled - && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) { - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); - mAnimationScheduled = true; + if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { + scheduleAnimation(); } } @@ -663,7 +636,14 @@ public class ValueAnimator extends Animator { @Override public void run() { 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; notifyStartListeners(); } - animationHandler.sendEmptyMessage(ANIMATION_START); + animationHandler.start(); } @Override @@ -1098,17 +1078,6 @@ public class ValueAnimator extends Animator { */ boolean animationFrame(long currentTime) { 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) { case RUNNING: case SEEKED: @@ -1144,6 +1113,31 @@ public class ValueAnimator extends Animator { } /** + * 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 * the most recent frame update on the animation. * diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 4866889..b319cd5 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -69,6 +69,12 @@ public final class Choreographer { 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); + + 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; @@ -84,7 +90,8 @@ public final class Choreographer { private final CallbackQueue[] mCallbackQueues; private boolean mFrameScheduled; - private long mLastFrameTime; + private boolean mCallbacksRunning; + private long mLastFrameTimeNanos; /** * Callback type: Input callback. Runs first. @@ -108,7 +115,7 @@ public final class Choreographer { mLooper = looper; mHandler = new FrameHandler(looper); mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; - mLastFrameTime = Long.MIN_VALUE; + mLastFrameTimeNanos = Long.MIN_VALUE; mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; 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) { if (!mFrameScheduled) { mFrameScheduled = true; @@ -289,7 +330,8 @@ public final class Choreographer { mHandler.sendMessageAtFrontOfQueue(msg); } } else { - final long nextFrameTime = Math.max(mLastFrameTime + sFrameDelay, now); + final long nextFrameTime = Math.max( + mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now); if (DEBUG) { 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) { if (!mFrameScheduled) { return; // no work to do } mFrameScheduled = false; - mLastFrameTime = SystemClock.uptimeMillis(); + mLastFrameTimeNanos = timestampNanos; + } + + final long startNanos; + if (DEBUG) { + startNanos = System.nanoTime(); } doCallbacks(Choreographer.CALLBACK_INPUT); @@ -314,20 +361,24 @@ public final class Choreographer { doCallbacks(Choreographer.CALLBACK_TRAVERSAL); if (DEBUG) { + final long endNanos = System.nanoTime(); 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) { - final long start; Callback callbacks; synchronized (mLock) { - start = SystemClock.uptimeMillis(); - callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(start); + final long now = SystemClock.uptimeMillis(); + callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now); + if (callbacks == null) { + return; + } + mCallbacksRunning = true; } - - if (callbacks != null) { + try { for (Callback c = callbacks; c != null; c = c.next) { if (DEBUG) { Log.d(TAG, "RunCallback: type=" + callbackType @@ -336,8 +387,9 @@ public final class Choreographer { } c.action.run(); } - + } finally { synchronized (mLock) { + mCallbacksRunning = false; do { final Callback next = callbacks.next; recycleCallbackLocked(callbacks); @@ -404,7 +456,7 @@ public final class Choreographer { public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_FRAME: - doFrame(0); + doFrame(System.nanoTime(), 0); break; case MSG_DO_SCHEDULE_VSYNC: doScheduleVsync(); @@ -423,7 +475,7 @@ public final class Choreographer { @Override public void onVsync(long timestampNanos, int frame) { - doFrame(frame); + doFrame(timestampNanos, frame); } } |