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

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 可合并。哈哈 是不是就是常见的动画结构

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

Android Framework 窗口子系统 的 分析主要分为以下部分: Android Framework 窗口子系统 (01)WindowMangerService基础知识 Android Framework 窗口子系统 (02) 应用进程和WMS之间的关系 Android Framework 窗口子系统 (03) 窗口显示次序 Android Fra... https://blog.csdn.net/wangyang55555/article/details/77877435 开发者选项中提供了“窗口动画缩放”、“过渡动画缩放”、“动画程序时长缩放”三个可供调整动画时长的菜单项。单从名字上很难分辨出这三个选项作用目标是啥,我们先把系统语言调整为English,对应于“Window anima... 一、前言 原文链接:https://www.jianshu.com/p/d41fe965e9e6android的WindowManagerService(简称wms)是系统框架一个非常庞大复杂的一个系统模块,它主要由三大块组成:wms数据结构,wms大遍历,wms的窗口动画wms总体图.pngwms数据结构就是wms的所有WindowState(继承windowconta... 本文主要介绍WindowContainer、Animatable、WindowContainerController、SurfaceControl、Transaction、SurfaceAnimator。 2.1Animatable、SurfaceAn... Android R: 记一次修复AOSP的Desktop(桌面模式)或者External Display(扩展投屏)点击最大化按钮,跳出其他应用App问题 原因分析: 抓取点击设置App的最大化按钮,窗口慢慢放大时的当时的log 2_log_click_Minimize_when_app_to_zoom_in__dumpsys_SurfaceFlinger.txt 此时发现设置App的层级已经跑到了桌面App和其他App的下面,壁纸的上面, 所以,就导致了该问题。 此时,发现设置App层级有两个: 实现同步应用转换 同步应用转换是 Android 9 中的一项功能,可以改进现有的应用转换架构。当用户打开、关闭应用或在应用之间切换时,SystemUI 或启动器(主屏幕)进程会发送逐帧控制动画的请求,同时保证在视图动画窗口动画之间进行同步。SystemUI 或启动器在动画过程中绘制新帧时,会在动画应用表面请求一个不同的转换,此转换可以确定应用在屏幕上的组成形式,并标记要与 SystemUI 或启动器目前正在绘制的帧同步的请求(表面事务)。 此过程可以实现无法在 Android 8.x 及更低版本中实 Android的Activity主窗口或者子窗口在显示或者退出的时候通常都有系统默认的窗口动画, 用户也可以自定义窗口动画的style resource;  窗口动画是由Android Framework中窗口管理器WindowManagerService实现的功能, 窗口动画是一个很复杂的多线程异步时序, 本文基于Android 6.0版本以窗口动画的一帧变化为例来说明Android F... 以上是xml文件的实现代码,总的来说还是比较简洁的,使用了LinearLayout,RelativeLayout布局方式,实现出来的静态效果如下所示。从视频效果中我们可以看到,这个欢迎界面的效果还是不错的,感兴趣的可以尝试一下,比较简单,下面会给出具体代码实现。想要实现动态效果,我们需要在MainActivity.java文件中去添加相关动画的配置,代码如下。我们发现,MainActivity,java中有相关的配置文件,配置文件在这里给出。1、首先第一步,我们需要在res目录下新建资源文件夹anim。.. Android Framework 窗口子系统 的 分析主要分为以下部分: Android Framework 窗口子系统 (01)WindowMangerService基础知识 Android Framework 窗口子系统 (02) 应用进程和WMS之间的关系 Android Framework 窗口子系统 (03) 窗口显示次序 Android Fra...