summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/animation/TimeAnimator.java10
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java96
-rw-r--r--core/java/android/view/Choreographer.java80
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);
}
}