Android系统中动画浅析

Android系统中动画浅析

Kelvin wuKelvin wu

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完成动画的单步执行。

总结

总的来讲动画的发源地就是编舞者,然后通过层层回调去完成各类动画的业务逻辑。

文章被以下专栏收录
还没有评论
推荐阅读