Android系统动画浅析
在Android系统中动画主要分为两类:窗口动画和窗口内部的APP动画
动画类别动画描述窗口动画应用窗口之间的切换动画,主要作用元素是以窗口为单位应用动画应用界面内部的一些动画,主要作用元素是以VIew为单位
动画在我们的日常工作中并不是一个核心的一个点,所以对其认识也不会太深。但是经过处理大量的性能和卡顿问题,如果对其有正确的理解,在分析问题的时候还是很有帮助的。
本文主要想阐述的方向是动画是如何驱动,主要从时间驱动这块讨论一下其原理。
动画的本质就是一幅画面在时间轴上不同的点呈现出不同的形态。注意这里的关键点是不同的时间点,好比View = f(t),随着输入参数t时间的变化,会根据一定的规则产生不同的View画面。
这里暂时还不区分窗口动画和应用动画,因为两者的时间驱动原理完全相同。
这里需要介绍一个类,叫编舞者,其基本原理就是能接受每次的Vsync信号,然后依次处理跟UI相关的事件(依次是Input(例如你的手指滑动)、动画、绘图)。
如上面这幅图,就是System_Server的android.display线程中处理编舞者的回调的一帧。
private final FrameDisplayEventReceiver mDisplayEventReceiver;
编舞者的成员变量:FrameDisplayEvenReceiver
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
839 implements Runnable {
840 private boolean mHavePendingVsync;
841 private long mTimestampNanos;
842 private int mFrame;
843
844 public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
845 super(looper, vsyncSource);
846 }
847
848 @Override
849 public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
850 // Ignore vsync from secondary display.
851 // This can be problematic because the call to scheduleVsync() is a one-shot.
852 // We need to ensure that we will still receive the vsync from the primary
853 // display which is the one we really care about. Ideally we should schedule
854 // vsync for a particular display.
855 // At this time Surface Flinger won't send us vsyncs for secondary displays
856 // but that could change in the future so let's log a message to help us remember
857 // that we need to fix this.
858 if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
859 Log.d(TAG, "Received vsync from secondary display, but we don't support "
860 + "this case yet. Choreographer needs a way to explicitly request "
861 + "vsync for a specific display to ensure it doesn't lose track "
862 + "of its scheduled vsync.");
863 scheduleVsync();
864 return;
865 }
866
867 // Post the vsync event to the Handler.
868 // The idea is to prevent incoming vsync events from completely starving
869 // the message queue. If there are no messages in the queue with timestamps
870 // earlier than the frame time, then the vsync event will be processed immediately.
871 // Otherwise, messages that predate the vsync event will be handled first.
872 long now = System.nanoTime();
873 if (timestampNanos > now) {
874 Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
875 + " ms in the future! Check that graphics HAL is generating vsync "
876 + "timestamps using the correct timebase.");
877 timestampNanos = now;
878 }
879
880 if (mHavePendingVsync) {
881 Log.w(TAG, "Already have a pending vsync event. There should only be "
882 + "one at a time.");
883 } else {
884 mHavePendingVsync = true;
885 }
886
887 mTimestampNanos = timestampNanos;
888 mFrame = frame;
889 Message msg = Message.obtain(mHandler, this);
890 msg.setAsynchronous(true);
891 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
892 }
893
894 @Override
895 public void run() {
896 mHavePendingVsync = false;
897 doFrame(mTimestampNanos, mFrame);
898 }
899 }
其基类叫做DisplayEventReceiver,根据其描述可以知道它是APP用以接收屏幕事件的,例如垂直同步信号。
/**
29 * Provides a low-level mechanism for an application to receive display events
30 * such as vertical sync.
31 *
32 * The display event receive is NOT thread safe. Moreover, its methods must only
33 * be called on the Looper thread to which it is attached.
34 *
35 * @hide
36 */
37public abstract class DisplayEventReceiver {
这个类存在一个重要函数需要在实现类中被override
/**
130 * Called when a vertical sync pulse is received.
131 * The recipient should render a frame and then call {@link #scheduleVsync}
132 * to schedule the next vertical sync pulse.
133 *
134 * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
135 * timebase.
136 * @param builtInDisplayId The surface flinger built-in display id such as
137 * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
138 * @param frame The frame number. Increases by one for each vertical sync interval.
139 */
140 public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
141 }
函数的三个参数分别为:vsync信号产生的时间、vsync信号产生的显示屏的id以及这一帧的序列号(这个序列号是依次递增的)
回到前面那一段子类的onVsync实现: 1、判断vsync信号的时间是否比当前时间更提前,要是提前的则怀疑是这个硬件信号出了问题,修正vsync信号的时间到当前时间 2、然后往Handler上抛一个异步消息,来运行自己,异步消息可以插队,尽量保证其他消息处理完之后优先处理Vsync信号的消息
因为FrameDisplayEventReceiver实现了runnable接口,其运行的函数就是doFrame,“制造一帧”。 由于动画就是各个帧之间画面有变化,所以理所当然动画就在这个doFrame中完成的,无论是窗口动画还是APP内动画都是基于这个原理来进行驱动的。
如果理解动画就是在特定的时间,对一幅图画做对应的变换。那么这个Vsync信号就是这个特定的时间。
一、APP内部动画
APP动画并不想介绍API的使用,主要想介绍的是doFrame怎么call到那些常用的动画API接口的 如上图所示,是微信在用户聊天界面打开一张图片的systrace信息,这种就是在APP的UI线程执行的动画,一般都是应用自己调用API来实现的动画效果,在动画开始和结束的时候都会使用异步trace来表明动画的时长,这种如果存在某一帧的耗时超过16ms,就会产生卡顿的现象。
通过搜索代码,可以知道这个异步trace的来源是ValueAnimator,在startAnimation和endAnimation的时候会开始和结束这个名字叫Animator的trace。
ValueAnimator的定义:
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
其实现了一个AnimationFrameCallback的接口
/**
* Callbacks that receives notifications for animation timing and frame commit timing.
*/
interface AnimationFrameCallback {
/**
* Run animation based on the frame time.
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
* base.
* @return if the animation has finished.
*/
boolean doAnimationFrame(long frameTime);
/**
* This notifies the callback of frame commit time. Frame commit time is the time after
* traversals happen, as opposed to the normal animation frame time that is before
* traversals. This is used to compensate expensive traversals that happen as the
* animation starts. When traversals take a long time to complete, the rendering of the
* initial frame will be delayed (by a long time). But since the startTime of the
* animation is set before the traversal, by the time of next frame, a lot of time would
* have passed since startTime was set, the animation will consequently skip a few frames
* to respect the new frameTime. By having the commit time, we can adjust the start time to
* when the first frame was drawn (after any expensive traversals) so that no frames
* will be skipped.
*
* @param frameTime The frame time after traversals happen, if any, in the
* {@link SystemClock#uptimeMillis()} time base.
*/
void commitAnimationFrame(long frameTime);
}
主要关注接口中第一个函数的实现:
1389 /**
1390 * Processes a frame of the animation, adjusting the start time if needed.
1391 *
1392 * @param frameTime The frame time.
1393 * @return true if the animation has ended.
1394 * @hide
1395 */
1396 public final boolean doAnimationFrame(long frameTime) {
1397 if (mStartTime < 0) {
1398 // First frame. If there is start delay, start delay count down will happen *after* this
1399 // frame.
1400 mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
1401 }
1402
1403 // Handle pause/resume
1404 if (mPaused) {
1405 mPauseTime = frameTime;
1406 removeAnimationCallback();
1407 return false;
1408 } else if (mResumed) {
1409 mResumed = false;
1410 if (mPauseTime > 0) {
1411 // Offset by the duration that the animation was paused
1412 mStartTime += (frameTime - mPauseTime);
1413 }
1414 }
1415
1416 if (!mRunning) {
1417 // If not running, that means the animation is in the start delay phase of a forward
1418 // running animation. In the case of reversing, we want to run start delay in the end.
1419 if (mStartTime > frameTime && mSeekFraction == -1) {
1420 // This is when no seek fraction is set during start delay. If developers change the
1421 // seek fraction during the delay, animation will start from the seeked position
1422 // right away.
1423 return false;
1424 } else {
1425 // If mRunning is not set by now, that means non-zero start delay,
1426 // no seeking, not reversing. At this point, start delay has passed.
1427 mRunning = true;
1428 startAnimation();
1429 }
1430 }
1431
1432 if (mLastFrameTime < 0) {
1433 if (mSeekFraction >= 0) {
1434 long seekTime = (long) (getScaledDuration() * mSeekFraction);
1435 mStartTime = frameTime - seekTime;
1436 mSeekFraction = -1;
1437 }
1438 mStartTimeCommitted = false; // allow start time to be compensated for jank
1439 }
1440 mLastFrameTime = frameTime;
1441 // The frame time might be before the start time during the first frame of
1442 // an animation. The "current time" must always be on or after the start
1443 // time to avoid animating frames at negative time intervals. In practice, this
1444 // is very rare and only happens when seeking backwards.
1445 final long currentTime = Math.max(frameTime, mStartTime);
1446 boolean finished = animateBasedOnTime(currentTime);
1447
1448 if (finished) {
1449 endAnimation();
1450 }
1451 return finished;
1452 }
其实这里这个函数就是vsync信号上来之后会回调到的函数
先从应用开发的思路来跟踪,一般创建好了动画并设定完成之后启动动画就会调用start()这个接口,这里的boolean类型的入参代表是否需要反复播放动画
/**
997 * Start the animation playing. This version of start() takes a boolean flag that indicates
998 * whether the animation should play in reverse. The flag is usually false, but may be set
999 * to true if called from the reverse() method.
1000 *
1001 * <p>The animation started by calling this method will be run on the thread that called
1002 * this method. This thread should have a Looper on it (a runtime exception will be thrown if
1003 * this is not the case). Also, if the animation will animate
1004 * properties of objects in the view hierarchy, then the calling thread should be the UI
1005 * thread for that view hierarchy.</p>
1006 *
1007 * @param playBackwards Whether the ValueAnimator should start playing in reverse.
1008 */
1009 private void start(boolean playBackwards) {
1010 if (Looper.myLooper() == null) {
1011 throw new AndroidRuntimeException("Animators may only be run on Looper threads");
1012 }
1013 mReversing = playBackwards;
1014 mSelfPulse = !mSuppressSelfPulseRequested;
1015 // Special case: reversing from seek-to-0 should act as if not seeked at all.
1016 if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
1017 if (mRepeatCount == INFINITE) {
1018 // Calculate the fraction of the current iteration.
1019 float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
1020 mSeekFraction = 1 - fraction;
1021 } else {
1022 mSeekFraction = 1 + mRepeatCount - mSeekFraction;
1023 }
1024 }
1025 mStarted = true;
1026 mPaused = false;
1027 mRunning = false;
1028 mAnimationEndRequested = false;
1029 // Resets mLastFrameTime when start() is called, so that if the animation was running,
1030 // calling start() would put the animation in the
1031 // started-but-not-yet-reached-the-first-frame phase.
1032 mLastFrameTime = -1;
1033 mFirstFrameTime = -1;
1034 mStartTime = -1;
1035 addAnimationCallback(0);
1036
1037 if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
1038 // If there's no start delay, init the animation and notify start listeners right away
1039 // to be consistent with the previous behavior. Otherwise, postpone this until the first
1040 // frame after the start delay.
1041 startAnimation();
1042 if (mSeekFraction == -1) {
1043 // No seek, start at play time 0. Note that the reason we are not using fraction 0
1044 // is because for animations with 0 duration, we want to be consistent with pre-N
1045 // behavior: skip to the final value immediately.
1046 setCurrentPlayTime(0);
1047 } else {
1048 setCurrentFraction(mSeekFraction);
1049 }
1050 }
1051 }
注意addAnimationCallback这个函数
1482 private void addAnimationCallback(long delay) {
1483 if (!mSelfPulse) {
1484 return;
1485 }
1486 getAnimationHandler().addAnimationFrameCallback(this, delay);
1487 }
往AnimationHandler中添加一个回调
/**
* Register to get a callback on the next frame after the delay.
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
主要就是把ValueAnimator添加到一个AnimationHandler的回调列表当中,那就很明显了,有事件触发AnimationHandler工作的时候就会去挨个调用回调
在其doAnimationFrame函数中会挨个调用
private 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() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
到这里可以确认这个函数肯定也是被回调的
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
这个函数是在编舞者的回调
getProvider().postFrameCallback(mFrameCallback);
这个回调是通过MyFrameCallbackProvider的postFrameCallback抛到编舞者上的,至于编舞者的回调介绍请参考其他文章
/**
* Default provider of timing pulse that uses Choreographer for frame callbacks.
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
所以到此就能理清APP的动画的驱动模式了: Vsync--->编舞者--->AnimationHandler--->ValueAnimator--->APP设定的回调
由于回调可以同时处理多个,所以也保证了这种工作方式可以在APP里同时执行多个动画
二、系统窗口动画
窗口动画比较核心的动画实现的类是AppWindowAnimator和WindowStateAnimator
这两个类都有一个函数stepAnimationLocked,代表单步动画,如果将这个函数中的缩放参数和alpha参数打印出来就能够知道当前的窗口的大小和透明度,对于去理解窗口切换时候的动画过程很有帮助。
/**
42 * Singleton class that carries out the animations and Surface operations in a separate task
43 * on behalf of WindowManagerService.
44 */
45public class WindowAnimator {
这里要提到一个WindowAnimator类,从描述中可以看出这个类是一个代表WMS的管理类,而不像APPWindowAnimator和WIndowStateAnimator属于是具体的某个窗口的动画
94 WindowAnimator(final WindowManagerService service) {
95 mService = service;
96 mContext = service.mContext;
97 mPolicy = service.mPolicy;
98 mWindowPlacerLocked = service.mWindowPlacerLocked;
99 AnimationThread.getHandler().runWithScissors(
100 () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
101
102 mAnimationFrameCallback = frameTimeNs -> {
103 synchronized (mService.mWindowMap) {
104 mAnimationFrameCallbackScheduled = false;
105 }
106 animate(frameTimeNs);
107 };
108 }
在其构造函数中就构造了一个回调函数去执行animate函数,不出意外的话这个回调函数会被抛到编舞者里去
400 void scheduleAnimation() {
401 if (!mAnimationFrameCallbackScheduled) {
402 mAnimationFrameCallbackScheduled = true;
403 mChoreographer.postFrameCallback(mAnimationFrameCallback);
404 }
405 }
这里会将回调抛到编舞者中
也就是说如果systemServer有动画执行就会跑到WIndowAnimator的animate函数当中去
animate函数执行流程很长,包括更新壁纸、转屏动画等逻辑均包含在其中 但是我们关心的是窗口切换的那类动画是如何执行的
dc.updateWindowsForAnimator(this);
注意到这里存在一个“为了动画更新窗口“函数
void updateWindowsForAnimator(WindowAnimator animator) {
mTmpWindowAnimator = animator;
forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
}
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
if (winAnimator.hasSurface()) {
final boolean wasAnimating = winAnimator.mWasAnimating;
final boolean nowAnimating = winAnimator.stepAnimationLocked(
mTmpWindowAnimator.mCurrentTime);
winAnimator.mWasAnimating = nowAnimating;
mTmpWindowAnimator.orAnimating(nowAnimating);
if (DEBUG_WALLPAPER) Slog.v(TAG,
w + ": wasAnimating=" + wasAnimating + ", nowAnimating=" + nowAnimating);
if (wasAnimating && !winAnimator.mAnimating
&& mWallpaperController.isWallpaperTarget(w)) {
mTmpWindowAnimator.mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
mService.mWindowPlacerLocked.debugLayoutRepeats(
"updateWindowsAndWallpaperLocked 2", pendingLayoutChanges);
}
}
}
final AppWindowToken atoken = w.mAppToken;
if (winAnimator.mDrawState == READY_TO_SHOW) {
if (atoken == null || atoken.allDrawn) {
if (w.performShowLocked()) {
pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
if (DEBUG_LAYOUT_REPEATS) {
mService.mWindowPlacerLocked.debugLayoutRepeats(
"updateWindowsAndWallpaperLocked 5", pendingLayoutChanges);
}
}
}
}
......
};
这里一开始就回去执行各个Window的stepAnimationLocked完成动画的单步执行。
总结
总的来讲动画的发源地就是编舞者,然后通过层层回调去完成各类动画的业务逻辑。