Choreographer.java revision 43ea54bdc343a913f62885304796e4ab1bca4ef1
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.view; 18 19import android.os.Handler; 20import android.os.Looper; 21import android.os.Message; 22import android.os.SystemClock; 23import android.os.SystemProperties; 24import android.util.Log; 25 26/** 27 * Coordinates animations and drawing for UI on a particular thread. 28 * 29 * This object is thread-safe. Other threads can post callbacks to run at a later time 30 * on the UI thread. 31 * 32 * Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver} 33 * can only be accessed from the UI thread so operations that touch the event receiver 34 * are posted to the UI thread if needed. 35 * 36 * @hide 37 */ 38public final class Choreographer { 39 private static final String TAG = "Choreographer"; 40 private static final boolean DEBUG = false; 41 42 // The default amount of time in ms between animation frames. 43 // When vsync is not enabled, we want to have some idea of how long we should 44 // wait before posting the next animation message. It is important that the 45 // default value be less than the true inter-frame delay on all devices to avoid 46 // situations where we might skip frames by waiting too long (we must compensate 47 // for jitter and hardware variations). Regardless of this value, the animation 48 // and display loop is ultimately rate-limited by how fast new graphics buffers can 49 // be dequeued. 50 private static final long DEFAULT_FRAME_DELAY = 10; 51 52 // The number of milliseconds between animation frames. 53 private static volatile long sFrameDelay = DEFAULT_FRAME_DELAY; 54 55 // Thread local storage for the choreographer. 56 private static final ThreadLocal<Choreographer> sThreadInstance = 57 new ThreadLocal<Choreographer>() { 58 @Override 59 protected Choreographer initialValue() { 60 Looper looper = Looper.myLooper(); 61 if (looper == null) { 62 throw new IllegalStateException("The current thread must have a looper!"); 63 } 64 return new Choreographer(looper); 65 } 66 }; 67 68 // System property to enable/disable vsync for animations and drawing. 69 // Enabled by default. 70 private static final boolean USE_VSYNC = SystemProperties.getBoolean( 71 "debug.choreographer.vsync", true); 72 73 // System property to enable/disable the use of the vsync / animation timer 74 // for drawing rather than drawing immediately. 75 // Temporarily disabled by default because postponing performTraversals() violates 76 // assumptions about traversals happening in-order relative to other posted messages. 77 // Bug: 5721047 78 private static final boolean USE_ANIMATION_TIMER_FOR_DRAW = SystemProperties.getBoolean( 79 "debug.choreographer.animdraw", false); 80 81 private static final int MSG_DO_ANIMATION = 0; 82 private static final int MSG_DO_DRAW = 1; 83 private static final int MSG_DO_SCHEDULE_VSYNC = 2; 84 private static final int MSG_DO_SCHEDULE_ANIMATION = 3; 85 private static final int MSG_DO_SCHEDULE_DRAW = 4; 86 87 private final Object mLock = new Object(); 88 89 private final Looper mLooper; 90 private final FrameHandler mHandler; 91 private final FrameDisplayEventReceiver mDisplayEventReceiver; 92 93 private Callback mCallbackPool; 94 95 private final CallbackQueue mAnimationCallbackQueue = new CallbackQueue(); 96 private final CallbackQueue mDrawCallbackQueue = new CallbackQueue(); 97 98 private boolean mAnimationScheduled; 99 private boolean mDrawScheduled; 100 private long mLastAnimationTime; 101 private long mLastDrawTime; 102 103 private Choreographer(Looper looper) { 104 mLooper = looper; 105 mHandler = new FrameHandler(looper); 106 mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; 107 mLastAnimationTime = Long.MIN_VALUE; 108 mLastDrawTime = Long.MIN_VALUE; 109 } 110 111 /** 112 * Gets the choreographer for the calling thread. Must be called from 113 * a thread that already has a {@link android.os.Looper} associated with it. 114 * 115 * @return The choreographer for this thread. 116 * @throws IllegalStateException if the thread does not have a looper. 117 */ 118 public static Choreographer getInstance() { 119 return sThreadInstance.get(); 120 } 121 122 /** 123 * The amount of time, in milliseconds, between each frame of the animation. This is a 124 * requested time that the animation will attempt to honor, but the actual delay between 125 * frames may be different, depending on system load and capabilities. This is a static 126 * function because the same delay will be applied to all animations, since they are all 127 * run off of a single timing loop. 128 * 129 * The frame delay may be ignored when the animation system uses an external timing 130 * source, such as the display refresh rate (vsync), to govern animations. 131 * 132 * @return the requested time between frames, in milliseconds 133 */ 134 public static long getFrameDelay() { 135 return sFrameDelay; 136 } 137 138 /** 139 * The amount of time, in milliseconds, between each frame of the animation. This is a 140 * requested time that the animation will attempt to honor, but the actual delay between 141 * frames may be different, depending on system load and capabilities. This is a static 142 * function because the same delay will be applied to all animations, since they are all 143 * run off of a single timing loop. 144 * 145 * The frame delay may be ignored when the animation system uses an external timing 146 * source, such as the display refresh rate (vsync), to govern animations. 147 * 148 * @param frameDelay the requested time between frames, in milliseconds 149 */ 150 public static void setFrameDelay(long frameDelay) { 151 sFrameDelay = frameDelay; 152 } 153 154 /** 155 * Subtracts typical frame delay time from a delay interval in milliseconds. 156 * 157 * This method can be used to compensate for animation delay times that have baked 158 * in assumptions about the frame delay. For example, it's quite common for code to 159 * assume a 60Hz frame time and bake in a 16ms delay. When we call 160 * {@link #postAnimationCallbackDelayed} we want to know how long to wait before 161 * posting the animation callback but let the animation timer take care of the remaining 162 * frame delay time. 163 * 164 * This method is somewhat conservative about how much of the frame delay it 165 * subtracts. It uses the same value returned by {@link #getFrameDelay} which by 166 * default is 10ms even though many parts of the system assume 16ms. Consequently, 167 * we might still wait 6ms before posting an animation callback that we want to run 168 * on the next frame, but this is much better than waiting a whole 16ms and likely 169 * missing the deadline. 170 * 171 * @param delayMillis The original delay time including an assumed frame delay. 172 * @return The adjusted delay time with the assumed frame delay subtracted out. 173 */ 174 public static long subtractFrameDelay(long delayMillis) { 175 final long frameDelay = sFrameDelay; 176 return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay; 177 } 178 179 /** 180 * Posts a callback to run on the next animation cycle. 181 * The callback only runs once and then is automatically removed. 182 * 183 * @param action The callback action to run during the next animation cycle. 184 * @param token The callback token, or null if none. 185 * 186 * @see #removeAnimationCallback 187 */ 188 public void postAnimationCallback(Runnable action, Object token) { 189 postAnimationCallbackDelayed(action, token, 0); 190 } 191 192 /** 193 * Posts a callback to run on the next animation cycle following the specified delay. 194 * The callback only runs once and then is automatically removed. 195 * 196 * @param action The callback action to run during the next animation cycle after 197 * the specified delay. 198 * @param token The callback token, or null if none. 199 * @param delayMillis The delay time in milliseconds. 200 * 201 * @see #removeAnimationCallback 202 */ 203 public void postAnimationCallbackDelayed(Runnable action, Object token, long delayMillis) { 204 if (action == null) { 205 throw new IllegalArgumentException("action must not be null"); 206 } 207 208 if (DEBUG) { 209 Log.d(TAG, "PostAnimationCallback: " + action + ", token=" + token 210 + ", delayMillis=" + delayMillis); 211 } 212 213 synchronized (mLock) { 214 final long now = SystemClock.uptimeMillis(); 215 final long dueTime = now + delayMillis; 216 mAnimationCallbackQueue.addCallbackLocked(dueTime, action, token); 217 218 if (dueTime <= now) { 219 scheduleAnimationLocked(now); 220 } else { 221 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_ANIMATION, action); 222 mHandler.sendMessageAtTime(msg, dueTime); 223 } 224 } 225 } 226 227 /** 228 * Removes animation callbacks that have the specified action and token. 229 * 230 * @param action The action property of the callbacks to remove, or null to remove 231 * callbacks with any action. 232 * @param token The token property of the callbacks to remove, or null to remove 233 * callbacks with any token. 234 * 235 * @see #postAnimationCallback 236 * @see #postAnimationCallbackDelayed 237 */ 238 public void removeAnimationCallbacks(Runnable action, Object token) { 239 if (DEBUG) { 240 Log.d(TAG, "RemoveAnimationCallbacks: " + action + ", token=" + token); 241 } 242 243 synchronized (mLock) { 244 mAnimationCallbackQueue.removeCallbacksLocked(action, token); 245 if (action != null && token == null) { 246 mHandler.removeMessages(MSG_DO_SCHEDULE_ANIMATION, action); 247 } 248 } 249 } 250 251 /** 252 * Posts a callback to run on the next draw cycle. 253 * The callback only runs once and then is automatically removed. 254 * 255 * @param action The callback action to run during the next draw cycle. 256 * @param token The callback token, or null if none. 257 * 258 * @see #removeDrawCallback 259 */ 260 public void postDrawCallback(Runnable action, Object token) { 261 postDrawCallbackDelayed(action, token, 0); 262 } 263 264 /** 265 * Posts a callback to run on the next draw cycle following the specified delay. 266 * The callback only runs once and then is automatically removed. 267 * 268 * @param action The callback action to run during the next animation cycle after 269 * the specified delay. 270 * @param token The callback token, or null if none. 271 * @param delayMillis The delay time in milliseconds. 272 * 273 * @see #removeDrawCallback 274 */ 275 public void postDrawCallbackDelayed(Runnable action, Object token, long delayMillis) { 276 if (action == null) { 277 throw new IllegalArgumentException("action must not be null"); 278 } 279 280 if (DEBUG) { 281 Log.d(TAG, "PostDrawCallback: " + action + ", token=" + token 282 + ", delayMillis=" + delayMillis); 283 } 284 285 synchronized (mLock) { 286 final long now = SystemClock.uptimeMillis(); 287 final long dueTime = now + delayMillis; 288 mDrawCallbackQueue.addCallbackLocked(dueTime, action, token); 289 scheduleDrawLocked(now); 290 291 if (dueTime <= now) { 292 scheduleDrawLocked(now); 293 } else { 294 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_DRAW, action); 295 mHandler.sendMessageAtTime(msg, dueTime); 296 } 297 } 298 } 299 300 /** 301 * Removes draw callbacks that have the specified action and token. 302 * 303 * @param action The action property of the callbacks to remove, or null to remove 304 * callbacks with any action. 305 * @param token The token property of the callbacks to remove, or null to remove 306 * callbacks with any token. 307 * 308 * @see #postDrawCallback 309 * @see #postDrawCallbackDelayed 310 */ 311 public void removeDrawCallbacks(Runnable action, Object token) { 312 if (DEBUG) { 313 Log.d(TAG, "RemoveDrawCallbacks: " + action + ", token=" + token); 314 } 315 316 synchronized (mLock) { 317 mDrawCallbackQueue.removeCallbacksLocked(action, token); 318 if (action != null && token == null) { 319 mHandler.removeMessages(MSG_DO_SCHEDULE_DRAW, action); 320 } 321 } 322 } 323 324 private void scheduleAnimationLocked(long now) { 325 if (!mAnimationScheduled) { 326 mAnimationScheduled = true; 327 if (USE_VSYNC) { 328 if (DEBUG) { 329 Log.d(TAG, "Scheduling vsync for animation."); 330 } 331 332 // If running on the Looper thread, then schedule the vsync immediately, 333 // otherwise post a message to schedule the vsync from the UI thread 334 // as soon as possible. 335 if (isRunningOnLooperThreadLocked()) { 336 scheduleVsyncLocked(); 337 } else { 338 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); 339 msg.setAsynchronous(true); 340 mHandler.sendMessageAtFrontOfQueue(msg); 341 } 342 } else { 343 final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now); 344 if (DEBUG) { 345 Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms."); 346 } 347 Message msg = mHandler.obtainMessage(MSG_DO_ANIMATION); 348 msg.setAsynchronous(true); 349 mHandler.sendMessageAtTime(msg, nextAnimationTime); 350 } 351 } 352 } 353 354 private void scheduleDrawLocked(long now) { 355 if (!mDrawScheduled) { 356 mDrawScheduled = true; 357 if (USE_ANIMATION_TIMER_FOR_DRAW) { 358 scheduleAnimationLocked(now); 359 } else { 360 if (DEBUG) { 361 Log.d(TAG, "Scheduling draw immediately."); 362 } 363 Message msg = mHandler.obtainMessage(MSG_DO_DRAW); 364 msg.setAsynchronous(true); 365 mHandler.sendMessageAtTime(msg, now); 366 } 367 } 368 } 369 370 void doAnimation() { 371 doAnimationInner(); 372 373 if (USE_ANIMATION_TIMER_FOR_DRAW) { 374 doDraw(); 375 } 376 } 377 378 void doAnimationInner() { 379 final long start; 380 Callback callbacks; 381 synchronized (mLock) { 382 if (!mAnimationScheduled) { 383 return; // no work to do 384 } 385 mAnimationScheduled = false; 386 387 start = SystemClock.uptimeMillis(); 388 if (DEBUG) { 389 Log.d(TAG, "Performing animation: " + Math.max(0, start - mLastAnimationTime) 390 + " ms have elapsed since previous animation."); 391 } 392 mLastAnimationTime = start; 393 394 callbacks = mAnimationCallbackQueue.extractDueCallbacksLocked(start); 395 } 396 397 if (callbacks != null) { 398 runCallbacks(callbacks); 399 synchronized (mLock) { 400 recycleCallbacksLocked(callbacks); 401 } 402 } 403 404 if (DEBUG) { 405 Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms."); 406 } 407 } 408 409 void doDraw() { 410 final long start; 411 Callback callbacks; 412 synchronized (mLock) { 413 if (!mDrawScheduled) { 414 return; // no work to do 415 } 416 mDrawScheduled = false; 417 418 start = SystemClock.uptimeMillis(); 419 if (DEBUG) { 420 Log.d(TAG, "Performing draw: " + Math.max(0, start - mLastDrawTime) 421 + " ms have elapsed since previous draw."); 422 } 423 mLastDrawTime = start; 424 425 callbacks = mDrawCallbackQueue.extractDueCallbacksLocked(start); 426 } 427 428 if (callbacks != null) { 429 runCallbacks(callbacks); 430 synchronized (mLock) { 431 recycleCallbacksLocked(callbacks); 432 } 433 } 434 435 if (DEBUG) { 436 Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms."); 437 } 438 } 439 440 void doScheduleVsync() { 441 synchronized (mLock) { 442 if (mAnimationScheduled) { 443 scheduleVsyncLocked(); 444 } 445 } 446 } 447 448 void doScheduleAnimation() { 449 synchronized (mLock) { 450 final long now = SystemClock.uptimeMillis(); 451 if (mAnimationCallbackQueue.hasDueCallbacksLocked(now)) { 452 scheduleAnimationLocked(now); 453 } 454 } 455 } 456 457 void doScheduleDraw() { 458 synchronized (mLock) { 459 final long now = SystemClock.uptimeMillis(); 460 if (mDrawCallbackQueue.hasDueCallbacksLocked(now)) { 461 scheduleDrawLocked(now); 462 } 463 } 464 } 465 466 private void scheduleVsyncLocked() { 467 mDisplayEventReceiver.scheduleVsync(); 468 } 469 470 private boolean isRunningOnLooperThreadLocked() { 471 return Looper.myLooper() == mLooper; 472 } 473 474 private void runCallbacks(Callback head) { 475 while (head != null) { 476 if (DEBUG) { 477 Log.d(TAG, "RunCallback: " + head.action + ", token=" + head.token 478 + ", waitMillis=" + (SystemClock.uptimeMillis() - head.dueTime)); 479 } 480 head.action.run(); 481 head = head.next; 482 } 483 } 484 485 private void recycleCallbacksLocked(Callback head) { 486 while (head != null) { 487 final Callback next = head.next; 488 recycleCallbackLocked(head); 489 head = next; 490 } 491 } 492 493 private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) { 494 Callback callback = mCallbackPool; 495 if (callback == null) { 496 callback = new Callback(); 497 } else { 498 mCallbackPool = callback.next; 499 callback.next = null; 500 } 501 callback.dueTime = dueTime; 502 callback.action = action; 503 callback.token = token; 504 return callback; 505 } 506 507 private void recycleCallbackLocked(Callback callback) { 508 callback.action = null; 509 callback.token = null; 510 callback.next = mCallbackPool; 511 mCallbackPool = callback; 512 } 513 514 private final class FrameHandler extends Handler { 515 public FrameHandler(Looper looper) { 516 super(looper); 517 } 518 519 @Override 520 public void handleMessage(Message msg) { 521 switch (msg.what) { 522 case MSG_DO_ANIMATION: 523 doAnimation(); 524 break; 525 case MSG_DO_DRAW: 526 doDraw(); 527 break; 528 case MSG_DO_SCHEDULE_VSYNC: 529 doScheduleVsync(); 530 break; 531 case MSG_DO_SCHEDULE_ANIMATION: 532 doScheduleAnimation(); 533 break; 534 case MSG_DO_SCHEDULE_DRAW: 535 doScheduleDraw(); 536 break; 537 } 538 } 539 } 540 541 private final class FrameDisplayEventReceiver extends DisplayEventReceiver { 542 public FrameDisplayEventReceiver(Looper looper) { 543 super(looper); 544 } 545 546 @Override 547 public void onVsync(long timestampNanos, int frame) { 548 doAnimation(); 549 } 550 } 551 552 private static final class Callback { 553 public Callback next; 554 public long dueTime; 555 public Runnable action; 556 public Object token; 557 } 558 559 private final class CallbackQueue { 560 private Callback mHead; 561 562 public boolean hasDueCallbacksLocked(long now) { 563 return mHead != null && mHead.dueTime <= now; 564 } 565 566 public Callback extractDueCallbacksLocked(long now) { 567 Callback callbacks = mHead; 568 if (callbacks == null || callbacks.dueTime > now) { 569 return null; 570 } 571 572 Callback last = callbacks; 573 Callback next = last.next; 574 while (next != null) { 575 if (next.dueTime > now) { 576 last.next = null; 577 break; 578 } 579 last = next; 580 next = next.next; 581 } 582 mHead = next; 583 return callbacks; 584 } 585 586 public void addCallbackLocked(long dueTime, Runnable action, Object token) { 587 Callback callback = obtainCallbackLocked(dueTime, action, token); 588 Callback entry = mHead; 589 if (entry == null) { 590 mHead = callback; 591 return; 592 } 593 if (dueTime < entry.dueTime) { 594 callback.next = entry; 595 mHead = callback; 596 return; 597 } 598 while (entry.next != null) { 599 if (dueTime < entry.next.dueTime) { 600 callback.next = entry.next; 601 break; 602 } 603 entry = entry.next; 604 } 605 entry.next = callback; 606 } 607 608 public void removeCallbacksLocked(Runnable action, Object token) { 609 Callback predecessor = null; 610 for (Callback callback = mHead; callback != null;) { 611 final Callback next = callback.next; 612 if ((action == null || callback.action == action) 613 && (token == null || callback.token == token)) { 614 if (predecessor != null) { 615 predecessor.next = next; 616 } else { 617 mHead = next; 618 } 619 recycleCallbackLocked(callback); 620 } else { 621 predecessor = callback; 622 } 623 callback = next; 624 } 625 } 626 } 627} 628