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