Choreographer.java revision 20c4f87b2916d05e860d11568d7db6b2d340e909
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 // Enable/disable vsync for animations and drawing. 69 private static final boolean USE_VSYNC = SystemProperties.getBoolean( 70 "debug.choreographer.vsync", true); 71 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 78 private static final int MSG_DO_FRAME = 0; 79 private static final int MSG_DO_SCHEDULE_VSYNC = 1; 80 private static final int MSG_DO_SCHEDULE_CALLBACK = 2; 81 82 private final Object mLock = new Object(); 83 84 private final Looper mLooper; 85 private final FrameHandler mHandler; 86 private final FrameDisplayEventReceiver mDisplayEventReceiver; 87 88 private Callback mCallbackPool; 89 90 private final CallbackQueue[] mCallbackQueues; 91 92 private boolean mFrameScheduled; 93 private boolean mCallbacksRunning; 94 private long mLastFrameTimeNanos; 95 96 /** 97 * Callback type: Input callback. Runs first. 98 */ 99 public static final int CALLBACK_INPUT = 0; 100 101 /** 102 * Callback type: Animation callback. Runs before traversals. 103 */ 104 public static final int CALLBACK_ANIMATION = 1; 105 106 /** 107 * Callback type: Traversal callback. Handles layout and draw. Runs last 108 * after all other asynchronous messages have been handled. 109 */ 110 public static final int CALLBACK_TRAVERSAL = 2; 111 112 private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL; 113 114 private Choreographer(Looper looper) { 115 mLooper = looper; 116 mHandler = new FrameHandler(looper); 117 mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null; 118 mLastFrameTimeNanos = Long.MIN_VALUE; 119 120 mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; 121 for (int i = 0; i <= CALLBACK_LAST; i++) { 122 mCallbackQueues[i] = new CallbackQueue(); 123 } 124 } 125 126 /** 127 * Gets the choreographer for the calling thread. Must be called from 128 * a thread that already has a {@link android.os.Looper} associated with it. 129 * 130 * @return The choreographer for this thread. 131 * @throws IllegalStateException if the thread does not have a looper. 132 */ 133 public static Choreographer getInstance() { 134 return sThreadInstance.get(); 135 } 136 137 /** 138 * The amount of time, in milliseconds, between each frame of the animation. This is a 139 * requested time that the animation will attempt to honor, but the actual delay between 140 * frames may be different, depending on system load and capabilities. This is a static 141 * function because the same delay will be applied to all animations, since they are all 142 * run off of a single timing loop. 143 * 144 * The frame delay may be ignored when the animation system uses an external timing 145 * source, such as the display refresh rate (vsync), to govern animations. 146 * 147 * @return the requested time between frames, in milliseconds 148 */ 149 public static long getFrameDelay() { 150 return sFrameDelay; 151 } 152 153 /** 154 * The amount of time, in milliseconds, between each frame of the animation. This is a 155 * requested time that the animation will attempt to honor, but the actual delay between 156 * frames may be different, depending on system load and capabilities. This is a static 157 * function because the same delay will be applied to all animations, since they are all 158 * run off of a single timing loop. 159 * 160 * The frame delay may be ignored when the animation system uses an external timing 161 * source, such as the display refresh rate (vsync), to govern animations. 162 * 163 * @param frameDelay the requested time between frames, in milliseconds 164 */ 165 public static void setFrameDelay(long frameDelay) { 166 sFrameDelay = frameDelay; 167 } 168 169 /** 170 * Subtracts typical frame delay time from a delay interval in milliseconds. 171 * 172 * This method can be used to compensate for animation delay times that have baked 173 * in assumptions about the frame delay. For example, it's quite common for code to 174 * assume a 60Hz frame time and bake in a 16ms delay. When we call 175 * {@link #postAnimationCallbackDelayed} we want to know how long to wait before 176 * posting the animation callback but let the animation timer take care of the remaining 177 * frame delay time. 178 * 179 * This method is somewhat conservative about how much of the frame delay it 180 * subtracts. It uses the same value returned by {@link #getFrameDelay} which by 181 * default is 10ms even though many parts of the system assume 16ms. Consequently, 182 * we might still wait 6ms before posting an animation callback that we want to run 183 * on the next frame, but this is much better than waiting a whole 16ms and likely 184 * missing the deadline. 185 * 186 * @param delayMillis The original delay time including an assumed frame delay. 187 * @return The adjusted delay time with the assumed frame delay subtracted out. 188 */ 189 public static long subtractFrameDelay(long delayMillis) { 190 final long frameDelay = sFrameDelay; 191 return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay; 192 } 193 194 /** 195 * Posts a callback to run on the next frame. 196 * The callback only runs once and then is automatically removed. 197 * 198 * @param callbackType The callback type. 199 * @param action The callback action to run during the next frame. 200 * @param token The callback token, or null if none. 201 * 202 * @see #removeCallbacks 203 */ 204 public void postCallback(int callbackType, Runnable action, Object token) { 205 postCallbackDelayed(callbackType, action, token, 0); 206 } 207 208 /** 209 * Posts a callback to run on the next frame following the specified delay. 210 * The callback only runs once and then is automatically removed. 211 * 212 * @param callbackType The callback type. 213 * @param action The callback action to run during the next frame after the specified delay. 214 * @param token The callback token, or null if none. 215 * @param delayMillis The delay time in milliseconds. 216 * 217 * @see #removeCallback 218 */ 219 public void postCallbackDelayed(int callbackType, 220 Runnable action, Object token, long delayMillis) { 221 if (action == null) { 222 throw new IllegalArgumentException("action must not be null"); 223 } 224 if (callbackType < 0 || callbackType > CALLBACK_LAST) { 225 throw new IllegalArgumentException("callbackType is invalid"); 226 } 227 228 if (DEBUG) { 229 Log.d(TAG, "PostCallback: type=" + callbackType 230 + ", action=" + action + ", token=" + token 231 + ", delayMillis=" + delayMillis); 232 } 233 234 synchronized (mLock) { 235 final long now = SystemClock.uptimeMillis(); 236 final long dueTime = now + delayMillis; 237 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 238 239 if (dueTime <= now) { 240 scheduleFrameLocked(now); 241 } else { 242 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 243 msg.arg1 = callbackType; 244 msg.setAsynchronous(true); 245 mHandler.sendMessageAtTime(msg, dueTime); 246 } 247 } 248 } 249 250 /** 251 * Removes callbacks that have the specified action and token. 252 * 253 * @param callbackType The callback type. 254 * @param action The action property of the callbacks to remove, or null to remove 255 * callbacks with any action. 256 * @param token The token property of the callbacks to remove, or null to remove 257 * callbacks with any token. 258 * 259 * @see #postCallback 260 * @see #postCallbackDelayed 261 */ 262 public void removeCallbacks(int callbackType, Runnable action, Object token) { 263 if (callbackType < 0 || callbackType > CALLBACK_LAST) { 264 throw new IllegalArgumentException("callbackType is invalid"); 265 } 266 267 if (DEBUG) { 268 Log.d(TAG, "RemoveCallbacks: type=" + callbackType 269 + ", action=" + action + ", token=" + token); 270 } 271 272 synchronized (mLock) { 273 mCallbackQueues[callbackType].removeCallbacksLocked(action, token); 274 if (action != null && token == null) { 275 mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action); 276 } 277 } 278 } 279 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 314 private void scheduleFrameLocked(long now) { 315 if (!mFrameScheduled) { 316 mFrameScheduled = true; 317 if (USE_VSYNC) { 318 if (DEBUG) { 319 Log.d(TAG, "Scheduling next frame on vsync."); 320 } 321 322 // If running on the Looper thread, then schedule the vsync immediately, 323 // otherwise post a message to schedule the vsync from the UI thread 324 // as soon as possible. 325 if (isRunningOnLooperThreadLocked()) { 326 scheduleVsyncLocked(); 327 } else { 328 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); 329 msg.setAsynchronous(true); 330 mHandler.sendMessageAtFrontOfQueue(msg); 331 } 332 } else { 333 final long nextFrameTime = Math.max( 334 mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now); 335 if (DEBUG) { 336 Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); 337 } 338 Message msg = mHandler.obtainMessage(MSG_DO_FRAME); 339 msg.setAsynchronous(true); 340 mHandler.sendMessageAtTime(msg, nextFrameTime); 341 } 342 } 343 } 344 345 void doFrame(long timestampNanos, int frame) { 346 synchronized (mLock) { 347 if (!mFrameScheduled) { 348 return; // no work to do 349 } 350 mFrameScheduled = false; 351 mLastFrameTimeNanos = timestampNanos; 352 } 353 354 final long startNanos; 355 if (DEBUG) { 356 startNanos = System.nanoTime(); 357 } 358 359 doCallbacks(Choreographer.CALLBACK_INPUT); 360 doCallbacks(Choreographer.CALLBACK_ANIMATION); 361 doCallbacks(Choreographer.CALLBACK_TRAVERSAL); 362 363 if (DEBUG) { 364 final long endNanos = System.nanoTime(); 365 Log.d(TAG, "Frame " + frame + ": Finished, took " 366 + (endNanos - startNanos) * 0.000001f + " ms, latency " 367 + (startNanos - timestampNanos) * 0.000001f + " ms."); 368 } 369 } 370 371 void doCallbacks(int callbackType) { 372 Callback callbacks; 373 synchronized (mLock) { 374 final long now = SystemClock.uptimeMillis(); 375 callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now); 376 if (callbacks == null) { 377 return; 378 } 379 mCallbacksRunning = true; 380 } 381 try { 382 for (Callback c = callbacks; c != null; c = c.next) { 383 if (DEBUG) { 384 Log.d(TAG, "RunCallback: type=" + callbackType 385 + ", action=" + c.action + ", token=" + c.token 386 + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); 387 } 388 c.action.run(); 389 } 390 } finally { 391 synchronized (mLock) { 392 mCallbacksRunning = false; 393 do { 394 final Callback next = callbacks.next; 395 recycleCallbackLocked(callbacks); 396 callbacks = next; 397 } while (callbacks != null); 398 } 399 } 400 } 401 402 void doScheduleVsync() { 403 synchronized (mLock) { 404 if (mFrameScheduled) { 405 scheduleVsyncLocked(); 406 } 407 } 408 } 409 410 void doScheduleCallback(int callbackType) { 411 synchronized (mLock) { 412 if (!mFrameScheduled) { 413 final long now = SystemClock.uptimeMillis(); 414 if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) { 415 scheduleFrameLocked(now); 416 } 417 } 418 } 419 } 420 421 private void scheduleVsyncLocked() { 422 mDisplayEventReceiver.scheduleVsync(); 423 } 424 425 private boolean isRunningOnLooperThreadLocked() { 426 return Looper.myLooper() == mLooper; 427 } 428 429 private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) { 430 Callback callback = mCallbackPool; 431 if (callback == null) { 432 callback = new Callback(); 433 } else { 434 mCallbackPool = callback.next; 435 callback.next = null; 436 } 437 callback.dueTime = dueTime; 438 callback.action = action; 439 callback.token = token; 440 return callback; 441 } 442 443 private void recycleCallbackLocked(Callback callback) { 444 callback.action = null; 445 callback.token = null; 446 callback.next = mCallbackPool; 447 mCallbackPool = callback; 448 } 449 450 private final class FrameHandler extends Handler { 451 public FrameHandler(Looper looper) { 452 super(looper); 453 } 454 455 @Override 456 public void handleMessage(Message msg) { 457 switch (msg.what) { 458 case MSG_DO_FRAME: 459 doFrame(System.nanoTime(), 0); 460 break; 461 case MSG_DO_SCHEDULE_VSYNC: 462 doScheduleVsync(); 463 break; 464 case MSG_DO_SCHEDULE_CALLBACK: 465 doScheduleCallback(msg.arg1); 466 break; 467 } 468 } 469 } 470 471 private final class FrameDisplayEventReceiver extends DisplayEventReceiver { 472 public FrameDisplayEventReceiver(Looper looper) { 473 super(looper); 474 } 475 476 @Override 477 public void onVsync(long timestampNanos, int frame) { 478 doFrame(timestampNanos, frame); 479 } 480 } 481 482 private static final class Callback { 483 public Callback next; 484 public long dueTime; 485 public Runnable action; 486 public Object token; 487 } 488 489 private final class CallbackQueue { 490 private Callback mHead; 491 492 public boolean hasDueCallbacksLocked(long now) { 493 return mHead != null && mHead.dueTime <= now; 494 } 495 496 public Callback extractDueCallbacksLocked(long now) { 497 Callback callbacks = mHead; 498 if (callbacks == null || callbacks.dueTime > now) { 499 return null; 500 } 501 502 Callback last = callbacks; 503 Callback next = last.next; 504 while (next != null) { 505 if (next.dueTime > now) { 506 last.next = null; 507 break; 508 } 509 last = next; 510 next = next.next; 511 } 512 mHead = next; 513 return callbacks; 514 } 515 516 public void addCallbackLocked(long dueTime, Runnable action, Object token) { 517 Callback callback = obtainCallbackLocked(dueTime, action, token); 518 Callback entry = mHead; 519 if (entry == null) { 520 mHead = callback; 521 return; 522 } 523 if (dueTime < entry.dueTime) { 524 callback.next = entry; 525 mHead = callback; 526 return; 527 } 528 while (entry.next != null) { 529 if (dueTime < entry.next.dueTime) { 530 callback.next = entry.next; 531 break; 532 } 533 entry = entry.next; 534 } 535 entry.next = callback; 536 } 537 538 public void removeCallbacksLocked(Runnable action, Object token) { 539 Callback predecessor = null; 540 for (Callback callback = mHead; callback != null;) { 541 final Callback next = callback.next; 542 if ((action == null || callback.action == action) 543 && (token == null || callback.token == token)) { 544 if (predecessor != null) { 545 predecessor.next = next; 546 } else { 547 mHead = next; 548 } 549 recycleCallbackLocked(callback); 550 } else { 551 predecessor = callback; 552 } 553 callback = next; 554 } 555 } 556 } 557} 558