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