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