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:
@@ -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);
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user