前言
Android 4.1的project butter中引入了VSYNC,三级缓存和Choreographer
(意为编舞者)机制,大幅度提升了Android显示的流畅性。本文将从project butter说起,进而介绍到Choreographer
源码实现。
从project butter说起
project butter引入VSYNC是处于什么考虑呢?首先我们需要知道screen tearing的概念,就是display在一行接一行从图形芯片读取并draw下一帧的时候,还没结束图形芯片就load了下一帧,这样就可能出现屏幕绘制的一帧上面一块是A,下面一块是B。如下图:
引入VSYNC可以告诉GPU在display drawing都执行完成之后再load下一帧,从而解决tearing现象。在Android Jelly Bean做的更彻底,利用VSYNC来作为触发CPU/GPU开始处理下一帧数据的脉冲。没有VSYNC的情况下,理想的视图是在每一帧的时候,cpu/gpu都处理完了下一帧要显示的内容,然后display顺利拿到下一帧进行绘制。但是由于cpu/gpu没有一个固定的触发处理下一帧的时机,所以可能会有下面的情况:
如图,由于下一帧开始处理的时机延迟,导致了当要展示第2帧的时候第2帧还没准备好,所以依然显示第1帧,出现卡顿。所以Android引入VSYNC作为触发CPU/GPU开始处理下一帧数据的脉冲。
这样的话当CPU/GPU帧数高于VSYNC帧数(60)的时候,不会出现卡顿。但是如果CPU/GPU帧数低于60呢?
由于在16.7ms内CPU/GPU没有处理完下一帧的数据,导致下一帧还是A。更糟糕的是由于使用的2级缓存(当前帧和下一帧),会导致后面每一帧都显示两帧的时间。所以project butter同时引入了三级缓存:当前帧,下一帧,下下一帧。
即使出现了在16.7ms内CPU/GPU没有处理完下一帧的数据那种情况,只要都能在下一个16.7ms处理完成那么就可以保证除第一帧外每一帧都不会卡顿。
Choreographer
就是用来控制Android UI在一秒60帧内一帧一帧进行显示的工具。
Choreographer源码分析
Choreographer
主要负责协调Android系统中animations, input and drawing的时机。The choreographer receives timing pulses (such as vertical synchronization) from the display subsystem then schedules work to occur as part of rendering the next display frame。
Choreographer常用API
如下是其public method1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// 获取当前线程的实例
static getInstance()
// post一个FrameCallback,到下一帧的时候执行一次
postFrameCallback(Choreographer.FrameCallback callback)
postFrameCallbackDelayed(Choreographer.FrameCallback callback, long delayMillis)
removeFrameCallback(Choreographer.FrameCallback callback)
/**
* Implement this interface to receive a callback when a new display frame is
* being rendered. The callback is invoked on the {@link Looper} thread to
* which the {@link Choreographer} is attached.
*/
public interface FrameCallback {
/**
* Called when a new display frame is being rendered.
* <p>
* This method provides the time in nanoseconds when the frame started being rendered.
* The frame time provides a stable time base for synchronizing animations
* and drawing. It should be used instead of {@link SystemClock#uptimeMillis()}
* or {@link System#nanoTime()} for animations and drawing in the UI. Using the frame
* time helps to reduce inter-frame jitter because the frame time is fixed at the time
* the frame was scheduled to start, regardless of when the animations or drawing
* callback actually runs. All callbacks that run as part of rendering a frame will
* observe the same frame time so using the frame time also helps to synchronize effects
* that are performed by different callbacks.
* </p><p>
* Please note that the framework already takes care to process animations and
* drawing using the frame time as a stable time base. Most applications should
* not need to use the frame time information directly.
* </p>
*
* @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
* in the {@link System#nanoTime()} timebase. Divide this value by {@code 1000000}
* to convert it to the {@link SystemClock#uptimeMillis()} time base.
*/
public void doFrame(long frameTimeNanos);
}
从getInstance说起
1 | /** |
可见是使用了ThreadLocal为每个线程保存了一份Choreographer实例。然后getInstance返回calling thread的实例。
其构造函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
其中:
- mLastFrameTimeNanos:是指上一次帧绘制时间点
- mFrameIntervalNanos:帧间时长,一般等于16.7ms。
- mHandler是FrameHandler实例,接收doFrame,doScheduleVSYNC,doScheduleCallback执行相应逻辑。
1 | private final class FrameHandler extends Handler { |
- mDisplayEventReceiver是FrameDisplayEventReceiver实例,接收到VSYNC信号后发送vsync event给handler处理。
1 | private final class FrameDisplayEventReceiver extends DisplayEventReceiver |
当FrameDisplayEventReceiver
接受到VSYNC后调用onVsync,然后发送包含自身的message的给handler,looper执行到这个message的时候调用run,执行doFrame。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
.......
// Set a limit to warn about skipped frames.
// Skipped frames imply jank.
private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
"debug.choreographer.skipwarning", 30);
从doframe
源码可以看到从FrameDisplayEventReceiver接收到VSYNC发送message(即自身)给handler开始到这个message真正开始执行之间如果超过了30帧,系统就会打出我们常见到的一行日志:1
2Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
告诉我们应用UI发生了卡顿。doFrame
方法接着还通过scheduleVsyncLocked
执行了一次VSYNC。1
2
3
4
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
然后依次执行了input,animation,traversal,commit这几个callback。
动画相关
ValueAnimator.start()
其start方法调用了带boolean参数的start方法,传入一个false
,表示该动画不会反向重复播放:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
通过addAnimationCallback(0);
给AnimationHanlder
添加了一个AnimationFrameCallback
:1
2
3
4
5
6private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
1 | /** |
getProvider()
返回一个AnimationFrameCallbackProvider
实例,其中通过代理方式持有Choreographer
从而添加FrameCallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* Default provider of timing pulse that uses Choreographer for frame callbacks.
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
public void postCommitCallback(Runnable runnable) {
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
}
public long getFrameTime() {
return mChoreographer.getFrameTime();
}
public long getFrameDelay() {
return Choreographer.getFrameDelay();
}
public void setFrameDelay(long delay) {
Choreographer.setFrameDelay(delay);
}
}
看到这里会不会觉得很奇怪?我们的动画不止一帧,但是这里只add了一次FrameCallback,那么我们还得看一下这个FrameCallback里到底卖了什么药。1
2
3
4
5
6
7
8
9
10// in AnimationHandler
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
看到这里应该清晰了,在调用addAnimationFrameCallback
的时候除了post了一个常量mFrameCallback
,还把传过来的参数AnimationFrameCallback
添加到了ArrayList中:1
2private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
new ArrayList<>();
mFrameCallback
在doFrame的时候只要mAnimationCallbacks
不为空,就接着post mFrameCallback
。同时调用mAnimationCallbacks
中每一个AnimationFrameCallback
的doAnimationFrame
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
从前面调用过程可知其实调用的就是ValueAnimator
的doAnimationFrame
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66/**
* Processes a frame of the animation, adjusting the start time if needed.
*
* @param frameTime The frame time.
* @return true if the animation has ended.
* @hide
*/
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing
? frameTime
: frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
if (mPaused) {
mPauseTime = frameTime;
removeAnimationCallback();
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
}
}
if (!mRunning) {
// If not running, that means the animation is in the start delay phase of a forward
// running animation. In the case of reversing, we want to run start delay in the end.
if (mStartTime > frameTime && mSeekFraction == -1) {
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
} else {
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
接着调用animateBasedOnTime
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/**
* This internal function processes a single animation frame for a given animation. The
* currentTime parameter is the timing pulse sent by the handler, used to calculate the
* elapsed duration, and therefore
* the elapsed fraction, of the animation. The return value indicates whether the animation
* should be ended (which happens when the elapsed time of the animation exceeds the
* animation's duration, including the repeatCount).
*
* @param currentTime The current time, as tracked by the static timing handler
* @return true if the animation's duration, including any repetitions due to
* <code>repeatCount</code> has been exceeded and the animation should be ended.
*/
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
if (scaledDuration == 0) {
// 0 duration animator, ignore the repeat count and skip to the end
done = true;
} else if (newIteration && !lastIterationFinished) {
// Time to repeat
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
} else if (lastIterationFinished) {
done = true;
}
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
计算出了 the fraction of the current iteration 以后调用animateValue(currentIterationFraction);
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* This method is called with the elapsed fraction of the animation during every
* animation frame. This function turns the elapsed fraction into an interpolated fraction
* and then into an animated value (from the evaluator. The function is called mostly during
* animation updates, but it is also called when the <code>end()</code>
* function is called, to set the final value on the property.
*
* <p>Overrides of this method must call the superclass to perform the calculation
* of the animated value.</p>
*
* @param fraction The elapsed fraction of the animation.
*/
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
上述两步都是在计算动画过程中View的一些value,通过一帧一帧绘制过程中View的变化视觉上串联成动画。然后可以看到依次调用所有UpdateListener的onAnimationUpdate,我们可以通过给动画添加一个AnimatorUpdateListener
监听每一帧,得到动画里所有计算后的value。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* Adds a listener to the set of listeners that are sent update events through the life of
* an animation. This method is called on all listeners for every frame of the animation,
* after the values for the animation have been calculated.
*
* @param listener the listener to be added to the current set of listeners for this animation.
*/
public void addUpdateListener(AnimatorUpdateListener listener) {
if (mUpdateListeners == null) {
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
}
mUpdateListeners.add(listener);
}
.......
/**
* Implementors of this interface can add themselves as update listeners
* to an <code>ValueAnimator</code> instance to receive callbacks on every animation
* frame, after the current frame's values have been calculated for that
* <code>ValueAnimator</code>.
*/
public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);
}
View.postOnAnimation()
1 | /** |