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