动画分析

我首先说哈,可能有人说我写的很烂,很不清楚或者很难懂,等于没讲,哈哈 我这里讲的可能对有些人来说写的好浅显易懂,对有些人只想知道各种细节的人就有些失望了,因为我一贯的思路都是看主干不看细节。有了主干 当想做某件事的时候才会去主干的某一个枝节细看。

android 内有很多类型 这里主要讲过度动画和窗口动画的实现原理,包括动画如何启动以及后续如何更新帧数据。

android 分了各种动画 比如过度动画或者窗口动画 然后过度动画又分了好多种什么Activity 打开动画 关闭动画等等,这里不要被这些花里胡哨的分类给搞昏了头,这里不管分了多少类,其实也只是区别场景而已,比如是app内子activity打开就为TRANSIT_OLD_ACTIVITY_OPEN ,然后应用间切换 其实本质也就是TASK 间切换 就为TRANSIT_OLD_TASK_OPEN(当然还有其他的 这里就只是举一个说明一下 不要咬文嚼字。 这些分类只是为了区分场景而已,不影响动画本身的实现。说到底过度动画和窗口动画实现的本质是一样的。

说了这些就来看一下这个动画实现的本质吧。我记忆力不好,一般我会去把问题看清他的实质,不去记忆一些细节,反正也记不住。

为了说清楚这个动画 我接下来会从下面几个方面来详细说:

  1. 动画作用对象
    也就动画的主体对象
  2. 如何启动动画
  3. 动画的帧回调如何实现的(也就是动画启动后如何持续跑起来直到结束)
  4. 每一帧数据如何更新
    这几个问题说明了 一个动画也就出来了对吧。

首先看动画作用对象吧:
无论是过渡动画还是窗口动画,实质上都是各个窗口容器(如 WindowState ActivityRecord)对自己的WindowContainer对应的SurfaceControl应用动画,然而直接对SurfaceControl做动画 又会存在不稳定的情况,因为动画线程一般不会持有WMS 全局锁(就算持有也不合理,主线程去等各种动画?那还不把system卡死)。也会导致同步困难,如我动画还没做完,systemServer线程就对SurfaceControl做移除相关操作,那就GG了。所以android引入对自己的WindowContainer对应的SurfaceControl引入leash SurfaceControl,动画作用在leash之上,这个leash 很简单就是在各个窗口容器和他的父亲节点之间插入一个节点(简单的说就是在链(本质上是树)上插入一个节点。

好再来看如何启动动画:
过度动画一般是作用在Task ,windowState ,activityrecord等WindowContainer
要启动动画就直接来看WindowContainer 的startAnimation 吧

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
        if (DEBUG_ANIM) {
            Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
        }

        // TODO: This should use isVisible() but because isVisible has a really weird meaning at
        // the moment this doesn't work for all animatable window containers.
        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);
    }

其实就是mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
mSurfaceFreezer);
看看定义

/**
     * Starts an animation.
     *
     * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
     *             component responsible for running the animation. It runs the animation with
     *             {@link AnimationAdapter#startAnimation} once the hierarchy with
     *             the Leash has been set up.
     * @param hidden Whether the container holding the child surfaces is currently visible or not.
     *               This is important as it will start with the leash hidden or visible before
     *               handing it to the component that is responsible to run the animation.
     * @param animationFinishedCallback The callback being triggered when the animation finishes.
     */
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {

额这些乱七八糟的说明,看不懂也没关系,可以不看 哈哈,我来说吧
Transaction t
这个Transaction 主要是负责保存作用在SurfaceControl的第一帧数据,并在随后适合的时机下发给SurfaceFlinger,了解即可

AnimationAdapter anim
这个比较重要,算是核心了,这个就是动画实际的帧处理(也可以说是实际动画的帧处理回调)也就是后面要说的第四点

int type
这个就是前面说的定义的各种动画类型了,不做过多说明了,没啥意义 ,为了区分动画场景,随你怎么理解了。

其他几个参数就不说了,这里你只需要知道一个关键参数anim 即可,这里这个虽然是anim,实际上他只是一个帧数据处理。我觉得这么叫更为合理。

好,我们继续
下一步就是

void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        if (surface == null) {
            Slog.w(TAG, "Unable to start animation, surface is null or no children.");
            cancelAnimation();
            return;
        }
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
        /**/ *重点:创建leash 对象,就是上一步的动画对象,具体怎么创建的自己看,很简单 没必要说前面也大致说了***
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        if (mAnimationStartDelayed) {
            if (DEBUG_ANIM) Slog.i(TAG, "Animation start delayed");
            return;
        }
        // 继续start
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
    }

就是说先创建动画的操作对象leash surfacecontrol

紧接着就是调用前面我们说的那个AnimationAdapter anim的 startAnimation

AnimationAdapter是个接口类,像窗口动画和过度动画大部分场景,其实现类为LocalAnimationAdapter
以窗口动画来说吧,前面为了简单,我们直接从WindowContainer的startAnimation来切入的

主要是为了不去看各个子类(WindowState, Task 等)在启动的时候各种乱七八糟的处理,我们既然是为了看清一个事情的本质,所谓本质我个人的理解就是事物的核心主干,不管一个人的皮囊各式各样,但是他的骨架大致都是一样的,只留下这个骨架我们才能不负重前行,血肉都抛弃了吧,需要的时候我们自己再为骨架添加血肉。

来看看WinddowState启动一个动画 其实主要就是创建一个LocalAnimationAdapter ,这个你也可以实现不同的LocalAnimationAdapter,我们只讲源生的了。
其需要初始化一个AnimationSpec 这里是一个WindowAnimationSpec 这个也是可以根据自己动画需要自己去实现不同的AnimationSpec

void startAnimation(Animation anim) {

        // If we are an inset provider, all our animations are driven by the inset client.
        if (mControllableInsetProvider != null) {
            return;
        }

        final DisplayInfo displayInfo = getDisplayInfo();
        anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                displayInfo.appWidth, displayInfo.appHeight);
        anim.restrictDuration(MAX_ANIMATION_DURATION);
        anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
        final AnimationAdapter adapter = new LocalAnimationAdapter(
                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
                        0 /* windowCornerRadius */),
                mWmService.mSurfaceAnimationRunner);
        startAnimation(getPendingTransaction(), adapter);
        commitPendingTransaction();
    }

我们就来看就是调用

private final SurfaceAnimationRunner mAnimator;
    private final AnimationSpec mSpec;
    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }

其就调用了SurfaceAnimationRunner (大部分过度动画都是共用的 mWmService.mSurfaceAnimationRunner)的startAnimation 参数传入AnimationSpec(以WindowAnimationSpec为例)

好了简单了,来看SurfaceAnimationRunner

void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
            Runnable finishCallback) {
        synchronized (mLock) {
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                    finishCallback);
            mPendingAnimations.put(animationLeash, runningAnim);
            if (!mAnimationStartDeferred) {
                mChoreographer.postFrameCallback(this::startAnimations);
            }

            // Some animations (e.g. move animations) require the initial transform to be applied
            // immediately.
            applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
        }
    }

好了简单了,只做了三件事
对即将启动的动画加入列表 mPendingAnimations.put(animationLeash, runningAnim);(实际上动画还没启动)
mPendingAnimations.put(animationLeash, runningAnim);
applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
如注释所说就是首先应用动画初始状态,也可以叫初始帧数据,随你怎么叫了,就是第一帧数据这里就设置了。
然后注册了个回调mChoreographer.postFrameCallback(this::startAnimations); 去真正的启动动画

mChoreographer 就是Choreographer ,这个对于熟悉动画的应该知道吧,简单可以理解为vsync的接收和处理回调的即可,动画本质上主要就是向Choreographer 注册回调,待vsync来了后在回调注册的回调去处理动画帧数据,反复循环,直到动画结束。

好了,再看看如何真正启动动画吧:

@GuardedBy("mLock")
    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();
        });

        anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        // TODO: change this back to use show instead of alpha when b/138459974 is
                        // fixed.
                        mFrameTransaction.setAlpha(a.mLeash, 1);
                    }
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {

                            // Post on other thread that we can push final state without jank.
                            mAnimationThreadHandler.post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);

        anim.start();
        if (a.mAnimSpec.canSkipFirstFrame()) {
            // If we can skip the first frame, we start one frame later.
            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
        }

        // Immediately start the animation by manually applying an animation frame. Otherwise, the
        // start time would only be set in the next frame, leading to a delay.
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

可以看到实际上是启动了个ValueAnimator 继承自Animator,这才是真正的动画发动机(哈哈也许这样说有人反驳,他并不是真正的发动机,因为里面的AnimationHander才是,无所谓了你开心就好,我就要这么讲),就是我们下面要讲的第三点。待会讲

所以启动动画最终就是启动一个Animator(哈哈 PS 竟然不是SurfaceAnimationRunner,我最初一直以为是SurfaceAnimationRunner,不好意思 我最初没怎么了解动画具体实现,所以开始我一直想在SurfaceAnimationRunner里面找到动画的持续回调(也就是第三点要讲的动画过程),然而并找不到,而且我也疑惑这里不是应该可以处理么?那为什么源生没有在这处理,开始我也很奇怪干嘛还要有ValueAnimator,其实啊这么设计是有道理的SurfaceAnimationRunner主要是负责启动动画的帧同步,另一方面如果所有的动画回调都在这处理,性能你能保证么?所以就有了ValueAnimator去处理各种的动画,个人理解哈 可能并不完全正确,但是不影响这个动画实现流程说明)。
然后呢

anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();
        });

注册一个帧处理回调
applyTransformation(a, mFrameTransaction, currentPlayTime); 这玩意就是我们要说的第四点。
待会讲。

突然不想讲了,因为到这了基本大部分人应该都明白了对吧。

哈哈还说说吧,那第三点动画怎么循环跑起来
那就得看ValueAnimator 的start

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

private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

哈哈 AnimationHandler出场了,没错他才是真正负责Choreographer ,发动机的主件(哈哈我是把他看成发动机的一部分了,无所谓了这玩意你想怎么理解就怎么理解,你说他是发动机也行,对吧,这玩意本身就是个定义,自己觉得怎么样对就怎么样定义吧)。

看一眼吧:

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));
        }
    }

这里就是getProvider().postFrameCallback(mFrameCallback); 不继续说了,有兴趣的自己看,我就说他是向Choreographer 注册回调了

那这里就是注册一个回调,如何让他循环起来呢,秘密就在mFrameCallback

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

这个就是一旦你把回调注册进来了,不移除,hander就会自动给你注册下一次回调。

好了第三点也出来了

第四点就很简单了
前面讲第二点的时候,也就是这里的doAnimationFrame ,就会回调第二点注册的addUpdateListener

最终也就是回调
SurfaceAnimationRunner 里的

private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
        a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
    }

其实也就是前面说的WindowAnimationSpec 的apply,(额不一定是WindowAnimationSpec,严格说就是AnimationSpec哈哈,我相信你懂得)。

就看一眼WindowAnimationSpec的apply呗

@Override
    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
        final TmpValues tmp = mThreadLocalTmps.get();
        tmp.transformation.clear();
        mAnimation.getTransformation(currentPlayTime, tmp.transformation);
        tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
        t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
        t.setAlpha(leash, tmp.transformation.getAlpha());

        boolean cropSet = false;
        if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
            if (tmp.transformation.hasClipRect()) {
                t.setWindowCrop(leash, tmp.transformation.getClipRect());
                cropSet = true;
            }
        } else {
            mTmpRect.set(mRootTaskBounds);
            if (tmp.transformation.hasClipRect()) {
                mTmpRect.intersect(tmp.transformation.getClipRect());
            }
            t.setWindowCrop(leash, mTmpRect);
            cropSet = true;
        }
        float cornerRadius = mWindowCornerRadius;
        if (mActivityThumbnailHelper != null) {
            final float curScaleX = tmp.floats[Matrix.MSCALE_X];
            final float curScaleY = tmp.floats[Matrix.MSCALE_Y];
            float scale = Math.max(curScaleX, curScaleY);
           
            float thumbLeashCornerRadius = cornerRadius;
            boolean isScaledThumbnail = 
            if (scale != 0.0f) {
                cornerRadius /=scale;
            }
            if (scale != 0.0f && isScaledThumbnail) {
                thumbLeashCornerRadius /= scale;
            }
            if (hasScaleWithClipAnimation) {
                mActivityThumbnailHelper.stepScaleUpDownAnimation(t, tmp.transformation, isScaledThumbnail);

            } 
            final SurfaceControl tempLeash = mActivityThumbnailHelper.getLeash();
            if (tempLeash != null && thumbLeashCornerRadius > 0.0f ) {
                t.setCornerRadius(tempLeash, thumbLeashCornerRadius);
            }
        }
        // END
        // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,
        // since it doesn't have anything it's relative to.
    }

嗯 简单 了,就是将private Animation mAnimation; 计算出来的数据设置给leash完事了

这样动画就跑起来了对吧。

好吧重点来了,总结下吧,我的简单说:

我就喜欢把这些玩意用自己的一句话说清楚,

不管是过度动画还是窗口动画就是就是把你定义的Animation,以AnimationSpec的形式封装,并创建对应的动画操作leash,然后通过SurfaceAnimationRunner启动一个ValueAnimator让你的动画跑起来就完事了,是不是相当简单。

额 所以问题就来了,窗口或者过度动画本质上是SurfaceCtrol动画,而且动画实质上和窗口以及SurfaceAnimationRunner 没任何鸟关系,我们只需要Animation ValueAnimator 以及leash 其实就完事了。甚至说只需要ValueAnimator 和leash 就完事了 因为ValueAnimator 和Animation 可合并。哈哈 是不是就是常见的动画结构

哈哈 是不是说成这样 觉得尼玛,这只剩下骨头确实有点丑了对吧,啥都不剩了。哈哈 本来就这样 事情你看透了 ,就啥也不是了。