Loading core/java/android/animation/TimeAnimator.java +0 −10 Original line number Diff line number Diff line Loading @@ -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); Loading core/java/android/animation/ValueAnimator.java +45 −51 Original line number Diff line number Diff line Loading @@ -55,11 +55,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. Loading Loading @@ -504,7 +499,7 @@ public class ValueAnimator extends Animator { mPlayingState = SEEKED; } mStartTime = currentTime - playTime; animationFrame(currentTime); doAnimationFrame(currentTime); } /** Loading @@ -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>(); Loading @@ -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 Loading @@ -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); } } Loading @@ -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) { Loading @@ -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(); } } Loading @@ -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; } } } Loading Loading @@ -935,7 +915,7 @@ public class ValueAnimator extends Animator { mRunning = true; notifyStartListeners(); } animationHandler.sendEmptyMessage(ANIMATION_START); animationHandler.start(); } @Override Loading Loading @@ -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: Loading Loading @@ -1143,6 +1112,31 @@ public class ValueAnimator extends Animator { 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 * the most recent frame update on the animation. Loading core/java/android/view/Choreographer.java +66 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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++) { Loading Loading @@ -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; Loading @@ -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."); } Loading @@ -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); Loading @@ -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; } if (callbacks != null) { mCallbacksRunning = true; } try { for (Callback c = callbacks; c != null; c = c.next) { if (DEBUG) { Log.d(TAG, "RunCallback: type=" + callbackType Loading @@ -336,8 +387,9 @@ public final class Choreographer { } c.action.run(); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { final Callback next = callbacks.next; recycleCallbackLocked(callbacks); Loading Loading @@ -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(); Loading @@ -423,7 +475,7 @@ public final class Choreographer { @Override public void onVsync(long timestampNanos, int frame) { doFrame(frame); doFrame(timestampNanos, frame); } } Loading Loading
core/java/android/animation/TimeAnimator.java +0 −10 Original line number Diff line number Diff line Loading @@ -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); Loading
core/java/android/animation/ValueAnimator.java +45 −51 Original line number Diff line number Diff line Loading @@ -55,11 +55,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. Loading Loading @@ -504,7 +499,7 @@ public class ValueAnimator extends Animator { mPlayingState = SEEKED; } mStartTime = currentTime - playTime; animationFrame(currentTime); doAnimationFrame(currentTime); } /** Loading @@ -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>(); Loading @@ -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 Loading @@ -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); } } Loading @@ -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) { Loading @@ -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(); } } Loading @@ -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; } } } Loading Loading @@ -935,7 +915,7 @@ public class ValueAnimator extends Animator { mRunning = true; notifyStartListeners(); } animationHandler.sendEmptyMessage(ANIMATION_START); animationHandler.start(); } @Override Loading Loading @@ -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: Loading Loading @@ -1143,6 +1112,31 @@ public class ValueAnimator extends Animator { 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 * the most recent frame update on the animation. Loading
core/java/android/view/Choreographer.java +66 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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++) { Loading Loading @@ -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; Loading @@ -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."); } Loading @@ -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); Loading @@ -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; } if (callbacks != null) { mCallbacksRunning = true; } try { for (Callback c = callbacks; c != null; c = c.next) { if (DEBUG) { Log.d(TAG, "RunCallback: type=" + callbackType Loading @@ -336,8 +387,9 @@ public final class Choreographer { } c.action.run(); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { final Callback next = callbacks.next; recycleCallbackLocked(callbacks); Loading Loading @@ -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(); Loading @@ -423,7 +475,7 @@ public final class Choreographer { @Override public void onVsync(long timestampNanos, int frame) { doFrame(frame); doFrame(timestampNanos, frame); } } Loading