Skip to content

Commit e19d5f0

Browse files
Jeff BrownAndroid (Google) Code Review
authored andcommitted
Merge "Use choreographer frame time to schedule animations." into jb-dev
2 parents 1b332db + 20c4f87 commit e19d5f0

File tree

3 files changed

+111
-75
lines changed

3 files changed

+111
-75
lines changed

core/java/android/animation/TimeAnimator.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ public class TimeAnimator extends ValueAnimator {
1414

1515
@Override
1616
boolean animationFrame(long currentTime) {
17-
if (mPlayingState == STOPPED) {
18-
mPlayingState = RUNNING;
19-
if (mSeekTime < 0) {
20-
mStartTime = currentTime;
21-
} else {
22-
mStartTime = currentTime - mSeekTime;
23-
// Now that we're playing, reset the seek time
24-
mSeekTime = -1;
25-
}
26-
}
2717
if (mListener != null) {
2818
long totalTime = currentTime - mStartTime;
2919
long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);

core/java/android/animation/ValueAnimator.java

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@ public class ValueAnimator extends Animator {
5555
*/
5656
private static float sDurationScale = 1.0f;
5757

58-
/**
59-
* Messages sent to timing handler: START is sent when an animation first begins.
60-
*/
61-
static final int ANIMATION_START = 0;
62-
6358
/**
6459
* Values used with internal variable mPlayingState to indicate the current state of an
6560
* animation.
@@ -504,7 +499,7 @@ public void setCurrentPlayTime(long playTime) {
504499
mPlayingState = SEEKED;
505500
}
506501
mStartTime = currentTime - playTime;
507-
animationFrame(currentTime);
502+
doAnimationFrame(currentTime);
508503
}
509504

510505
/**
@@ -528,8 +523,9 @@ public long getCurrentPlayTime() {
528523
* the same times for calculating their values, which makes synchronizing
529524
* animations possible.
530525
*
526+
* The handler uses the Choreographer for executing periodic callbacks.
531527
*/
532-
private static class AnimationHandler extends Handler implements Runnable {
528+
private static class AnimationHandler implements Runnable {
533529
// The per-thread list of all active animations
534530
private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
535531

@@ -552,34 +548,13 @@ private AnimationHandler() {
552548
}
553549

554550
/**
555-
* The START message is sent when an animation's start() method is called.
556-
* It cannot start synchronously when start() is called
557-
* because the call may be on the wrong thread, and it would also not be
558-
* synchronized with other animations because it would not start on a common
559-
* timing pulse. So each animation sends a START message to the handler, which
560-
* causes the handler to place the animation on the active animations queue and
561-
* start processing frames for that animation.
551+
* Start animating on the next frame.
562552
*/
563-
@Override
564-
public void handleMessage(Message msg) {
565-
switch (msg.what) {
566-
case ANIMATION_START:
567-
// If there are already active animations, or if another ANIMATION_START
568-
// message was processed during this frame, then the pending list may already
569-
// have been cleared. If that's the case, we've already processed the
570-
// active animations for this frame - don't do it again.
571-
if (mPendingAnimations.size() > 0) {
572-
doAnimationFrame();
573-
}
574-
break;
575-
}
553+
public void start() {
554+
scheduleAnimation();
576555
}
577556

578-
private void doAnimationFrame() {
579-
// currentTime holds the common time for all animations processed
580-
// during this frame
581-
long currentTime = AnimationUtils.currentAnimationTimeMillis();
582-
557+
private void doAnimationFrame(long frameTime) {
583558
// mPendingAnimations holds any animations that have requested to be started
584559
// We're going to clear mPendingAnimations, but starting animation may
585560
// cause more to be added to the pending list (for example, if one animation
@@ -605,7 +580,7 @@ private void doAnimationFrame() {
605580
int numDelayedAnims = mDelayedAnims.size();
606581
for (int i = 0; i < numDelayedAnims; ++i) {
607582
ValueAnimator anim = mDelayedAnims.get(i);
608-
if (anim.delayedAnimationFrame(currentTime)) {
583+
if (anim.delayedAnimationFrame(frameTime)) {
609584
mReadyAnims.add(anim);
610585
}
611586
}
@@ -626,7 +601,7 @@ private void doAnimationFrame() {
626601
int i = 0;
627602
while (i < numAnims) {
628603
ValueAnimator anim = mAnimations.get(i);
629-
if (anim.animationFrame(currentTime)) {
604+
if (anim.doAnimationFrame(frameTime)) {
630605
mEndingAnims.add(anim);
631606
}
632607
if (mAnimations.size() == numAnims) {
@@ -652,18 +627,23 @@ private void doAnimationFrame() {
652627

653628
// If there are still active or delayed animations, schedule a future call to
654629
// onAnimate to process the next frame of the animations.
655-
if (!mAnimationScheduled
656-
&& (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) {
657-
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
658-
mAnimationScheduled = true;
630+
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
631+
scheduleAnimation();
659632
}
660633
}
661634

662635
// Called by the Choreographer.
663636
@Override
664637
public void run() {
665638
mAnimationScheduled = false;
666-
doAnimationFrame();
639+
doAnimationFrame(mChoreographer.getFrameTime());
640+
}
641+
642+
private void scheduleAnimation() {
643+
if (!mAnimationScheduled) {
644+
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
645+
mAnimationScheduled = true;
646+
}
667647
}
668648
}
669649

@@ -935,7 +915,7 @@ private void start(boolean playBackwards) {
935915
mRunning = true;
936916
notifyStartListeners();
937917
}
938-
animationHandler.sendEmptyMessage(ANIMATION_START);
918+
animationHandler.start();
939919
}
940920

941921
@Override
@@ -1098,17 +1078,6 @@ private boolean delayedAnimationFrame(long currentTime) {
10981078
*/
10991079
boolean animationFrame(long currentTime) {
11001080
boolean done = false;
1101-
1102-
if (mPlayingState == STOPPED) {
1103-
mPlayingState = RUNNING;
1104-
if (mSeekTime < 0) {
1105-
mStartTime = currentTime;
1106-
} else {
1107-
mStartTime = currentTime - mSeekTime;
1108-
// Now that we're playing, reset the seek time
1109-
mSeekTime = -1;
1110-
}
1111-
}
11121081
switch (mPlayingState) {
11131082
case RUNNING:
11141083
case SEEKED:
@@ -1143,6 +1112,31 @@ boolean animationFrame(long currentTime) {
11431112
return done;
11441113
}
11451114

1115+
/**
1116+
* Processes a frame of the animation, adjusting the start time if needed.
1117+
*
1118+
* @param frameTime The frame time.
1119+
* @return true if the animation has ended.
1120+
*/
1121+
final boolean doAnimationFrame(long frameTime) {
1122+
if (mPlayingState == STOPPED) {
1123+
mPlayingState = RUNNING;
1124+
if (mSeekTime < 0) {
1125+
mStartTime = frameTime;
1126+
} else {
1127+
mStartTime = frameTime - mSeekTime;
1128+
// Now that we're playing, reset the seek time
1129+
mSeekTime = -1;
1130+
}
1131+
}
1132+
// The frame time might be before the start time during the first frame of
1133+
// an animation. The "current time" must always be on or after the start
1134+
// time to avoid animating frames at negative time intervals. In practice, this
1135+
// is very rare and only happens when seeking backwards.
1136+
final long currentTime = Math.max(frameTime, mStartTime);
1137+
return animationFrame(currentTime);
1138+
}
1139+
11461140
/**
11471141
* Returns the current animation fraction, which is the elapsed/interpolated fraction used in
11481142
* the most recent frame update on the animation.

core/java/android/view/Choreographer.java

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ protected Choreographer initialValue() {
6969
private static final boolean USE_VSYNC = SystemProperties.getBoolean(
7070
"debug.choreographer.vsync", true);
7171

72+
// Enable/disable using the frame time instead of returning now.
73+
private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
74+
"debug.choreographer.frametime", true);
75+
76+
private static final long NANOS_PER_MS = 1000000;
77+
7278
private static final int MSG_DO_FRAME = 0;
7379
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
7480
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
@@ -84,7 +90,8 @@ protected Choreographer initialValue() {
8490
private final CallbackQueue[] mCallbackQueues;
8591

8692
private boolean mFrameScheduled;
87-
private long mLastFrameTime;
93+
private boolean mCallbacksRunning;
94+
private long mLastFrameTimeNanos;
8895

8996
/**
9097
* Callback type: Input callback. Runs first.
@@ -108,7 +115,7 @@ private Choreographer(Looper looper) {
108115
mLooper = looper;
109116
mHandler = new FrameHandler(looper);
110117
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
111-
mLastFrameTime = Long.MIN_VALUE;
118+
mLastFrameTimeNanos = Long.MIN_VALUE;
112119

113120
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
114121
for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -270,6 +277,40 @@ public void removeCallbacks(int callbackType, Runnable action, Object token) {
270277
}
271278
}
272279

280+
/**
281+
* Gets the time when the current frame started. The frame time should be used
282+
* instead of {@link SystemClock#uptimeMillis()} to synchronize animations.
283+
* This helps to reduce inter-frame jitter because the frame time is fixed at the
284+
* time the frame was scheduled to start, regardless of when the animations or
285+
* drawing code actually ran.
286+
*
287+
* This method should only be called from within a callback.
288+
*
289+
* @return The frame start time, in the {@link SystemClock#uptimeMillis()} time base.
290+
*
291+
* @throws IllegalStateException if no frame is in progress.
292+
*/
293+
public long getFrameTime() {
294+
return getFrameTimeNanos() / NANOS_PER_MS;
295+
}
296+
297+
/**
298+
* Same as {@link #getFrameTime()} but with nanosecond precision.
299+
*
300+
* @return The frame start time, in the {@link System#nanoTime()} time base.
301+
*
302+
* @throws IllegalStateException if no frame is in progress.
303+
*/
304+
public long getFrameTimeNanos() {
305+
synchronized (mLock) {
306+
if (!mCallbacksRunning) {
307+
throw new IllegalStateException("This method must only be called as "
308+
+ "part of a callback while a frame is in progress.");
309+
}
310+
return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
311+
}
312+
}
313+
273314
private void scheduleFrameLocked(long now) {
274315
if (!mFrameScheduled) {
275316
mFrameScheduled = true;
@@ -289,7 +330,8 @@ private void scheduleFrameLocked(long now) {
289330
mHandler.sendMessageAtFrontOfQueue(msg);
290331
}
291332
} else {
292-
final long nextFrameTime = Math.max(mLastFrameTime + sFrameDelay, now);
333+
final long nextFrameTime = Math.max(
334+
mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
293335
if (DEBUG) {
294336
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
295337
}
@@ -300,34 +342,43 @@ private void scheduleFrameLocked(long now) {
300342
}
301343
}
302344

303-
void doFrame(int frame) {
345+
void doFrame(long timestampNanos, int frame) {
304346
synchronized (mLock) {
305347
if (!mFrameScheduled) {
306348
return; // no work to do
307349
}
308350
mFrameScheduled = false;
309-
mLastFrameTime = SystemClock.uptimeMillis();
351+
mLastFrameTimeNanos = timestampNanos;
352+
}
353+
354+
final long startNanos;
355+
if (DEBUG) {
356+
startNanos = System.nanoTime();
310357
}
311358

312359
doCallbacks(Choreographer.CALLBACK_INPUT);
313360
doCallbacks(Choreographer.CALLBACK_ANIMATION);
314361
doCallbacks(Choreographer.CALLBACK_TRAVERSAL);
315362

316363
if (DEBUG) {
364+
final long endNanos = System.nanoTime();
317365
Log.d(TAG, "Frame " + frame + ": Finished, took "
318-
+ (SystemClock.uptimeMillis() - mLastFrameTime) + " ms.");
366+
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
367+
+ (startNanos - timestampNanos) * 0.000001f + " ms.");
319368
}
320369
}
321370

322371
void doCallbacks(int callbackType) {
323-
final long start;
324372
Callback callbacks;
325373
synchronized (mLock) {
326-
start = SystemClock.uptimeMillis();
327-
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(start);
374+
final long now = SystemClock.uptimeMillis();
375+
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
376+
if (callbacks == null) {
377+
return;
378+
}
379+
mCallbacksRunning = true;
328380
}
329-
330-
if (callbacks != null) {
381+
try {
331382
for (Callback c = callbacks; c != null; c = c.next) {
332383
if (DEBUG) {
333384
Log.d(TAG, "RunCallback: type=" + callbackType
@@ -336,8 +387,9 @@ void doCallbacks(int callbackType) {
336387
}
337388
c.action.run();
338389
}
339-
390+
} finally {
340391
synchronized (mLock) {
392+
mCallbacksRunning = false;
341393
do {
342394
final Callback next = callbacks.next;
343395
recycleCallbackLocked(callbacks);
@@ -404,7 +456,7 @@ public FrameHandler(Looper looper) {
404456
public void handleMessage(Message msg) {
405457
switch (msg.what) {
406458
case MSG_DO_FRAME:
407-
doFrame(0);
459+
doFrame(System.nanoTime(), 0);
408460
break;
409461
case MSG_DO_SCHEDULE_VSYNC:
410462
doScheduleVsync();
@@ -423,7 +475,7 @@ public FrameDisplayEventReceiver(Looper looper) {
423475

424476
@Override
425477
public void onVsync(long timestampNanos, int frame) {
426-
doFrame(frame);
478+
doFrame(timestampNanos, frame);
427479
}
428480
}
429481

0 commit comments

Comments
 (0)