【Android 13源码分析】应用启动动画-app_transition-4

1,121 阅读10分钟

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。

                        -- 服装学院的IT男

本篇为应用启动动画 app_transition 的最后一篇,上一篇的流程已经到了 system_server 开始跨进程调用 launcher ,现在就要真的在 launcher 执行执行动画了,这是本篇需要看的流程完整的调用链:

IRemoteAnimationRunner.Stub::onAnimationStart
    LauncherAnimationRunner::onAnimationStart
        LauncherAnimationRunner.AnimationResult::init  --结束回调
        QuickstepTransitionManager.AppLaunchAnimationRunner::onAnimationStart
            QuickstepTransitionManager.AppLaunchAnimationRunner::composeIconLaunchAnimator
                QuickstepTransitionManager.AppLaunchAnimationRunner::getOpeningWindowAnimators
                    MultiValueUpdateListener::onUpdate
                        SurfaceParams.Builder::build                 -- 构建动画参数SurfaceParams
                        SurfaceTransactionApplier::scheduleApply     -- 应用参数到leash
                            ViewRootImpl::mergeWithNextTransaction   -- Transaction的apply
            LauncherAnimationRunner.AnimationResult::setAnimation
                AnimatorListenerAdapter::onAnimationEnd
                    LauncherAnimationRunner.AnimationResult::finish  -- 触发结束回调执行

1. launcher开始动画

先回忆下上面末尾 RemoteAnimationController::goodToGo 方法里最后执行的一句代码

    mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
                            wallpaperTargets, nonAppTargets, mFinishedCallback);

再次看到了 RemoteAnimationController 下的 mRemoteAnimationAdapter 应该非常清楚,就是【动画-app_transition-1】末尾提到的,就是 launcher 传递过来的 RemoteAnimationAdapter。

这里需要回顾一下之前 launcher 里几个关键类的关系图:

launcher构建的几个类的包含关系.png

RemoteAnimationAdapter 的 getRunner() 返回的就是 RemoteAnimationAdapterCompat 下 wrapRemoteAnimationRunner 返回的匿名 IRemoteAnimationRunner.Stub 对象。 现在重新在看看这个方法。

# RemoteAnimationAdapterCompat
    // 参数remoteAnimationAdapter是LauncherAnimationRunne对象

    public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
        return new IRemoteAnimationRunner.Stub() {
            @Override
            public void onAnimationStart(@TransitionOldType int transit,
                    RemoteAnimationTarget[] apps,
                    RemoteAnimationTarget[] wallpapers,
                    RemoteAnimationTarget[] nonApps,
                    final IRemoteAnimationFinishedCallback finishedCallback) {
                final RemoteAnimationTargetCompat[] appsCompat =
                        RemoteAnimationTargetCompat.wrap(apps);
                final RemoteAnimationTargetCompat[] wallpapersCompat =
                        RemoteAnimationTargetCompat.wrap(wallpapers);
                final RemoteAnimationTargetCompat[] nonAppsCompat =
                        RemoteAnimationTargetCompat.wrap(nonApps);
                // 定义个动画结束的回调,也就是执行参数finishedCallback的onAnimationFinished回调
                final Runnable animationFinishedCallback = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            finishedCallback.onAnimationFinished();
                        } catch (RemoteException e) {
                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
                                    + " finished callback", e);
                        }
                    }
                };
                // * 实际上是调用 LauncherAnimationRunner 的 onAnimationStart
                remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat,
                        nonAppsCompat, animationFinishedCallback);
            }

            @Override
            public void onAnimationCancelled(boolean isKeyguardOccluded) {
                // 实际上是调用LauncherAnimationRunner的onAnimationCancelled
                remoteAnimationAdapter.onAnimationCancelled();
            }
        };
    }

根据在 launcher 部分的介绍,直接看 LauncherAnimationRunner

# LauncherAnimationRunner
    @BinderThread
    public void onAnimationStart(
            int transit,
            RemoteAnimationTargetCompat[] appTargets, // app目标 2个(桌面和电话)
            RemoteAnimationTargetCompat[] wallpaperTargets, 壁纸目标 1个
            RemoteAnimationTargetCompat[] nonAppTargets, 非应用目标 0个
            Runnable runnable) {  // 根据调用过来的地方知道是动画结束的回调
        // 构建一个Runnable
        Runnable r = () -> {
            // 要开始新的动画,所以先执行动画清理工作
            finishExistingAnimation();
            // 将动画结束的回调包装在AnimationResult中
            mAnimationResult = new AnimationResult(() -> mAnimationResult = null, runnable);
            // 通过工厂模式构建对象并执行 onCreateAnimation
            getFactory().onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets,
                    mAnimationResult);
        };
        // post到下次刷新执行Runnable
        if (mStartAtFrontOfQueue) {
            postAtFrontOfQueueAsynchronously(mHandler, r);
        } else {
            postAsyncCallback(mHandler, r);
        }
    }

这个方法比较重要,都加上了注释,这里 mStartAtFrontOfQueue 是在构建 LauncherAnimationRunner 的时候赋值的, 根据前面的分析传递的是 true 。其实不管 true 还是 fail 都会将构建的Runnable 对象 post 到下一次刷新的时候执行,为 true 的话就表示插队到前面。这个可以不用过于在意,知道是在下一帧的时候会执行 Runnable 即可。

主要看的还是看“getFactory().onCreateAnimation”方法,另外关注新创建的 AnimationResult 什么时候用就表示什么时候动画结束。

"getFactory()"之前也分析过所以直接看 AppLaunchAnimationRunner::onCreateAnimation 即可

# QuickstepTransitionManager.AppLaunchAnimationRunner

        @Override
        public void onCreateAnimation(int transit,
                RemoteAnimationTargetCompat[] appTargets,
                RemoteAnimationTargetCompat[] wallpaperTargets,
                RemoteAnimationTargetCompat[] nonAppTargets,
                LauncherAnimationRunner.AnimationResult result) {
            // 创建个动画集合
            AnimatorSet anim = new AnimatorSet();
            ...... // 忽略从小部件或者recent打开的分支
            // 当前分支
            composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
                        launcherClosing);
            ......
            // 监听动画结束回调,执行 system_server 动画结束的回调
            result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,
                    skipFirstFrame);
        }
    private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
            @NonNull RemoteAnimationTargetCompat[] appTargets,
            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
            @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
            boolean launcherClosing) {
                ......
                // 获取动画
                Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets,
                nonAppTargets, windowTargetBounds, areAllTargetsTranslucent(appTargets),
                rotationChange);
                windowAnimator.setStartDelay(startDelay);
                // 播放动画
                anim.play(windowAnimator);
                ......
            }

这里看到了动画的play,那么关键是在 getOpeningWindowAnimators 方法里。

1.1 动画的创建

# QuickstepTransitionManager.AppLaunchAnimationRunner

    private Animator getOpeningWindowAnimators(View v,
            RemoteAnimationTargetCompat[] appTargets,
            RemoteAnimationTargetCompat[] wallpaperTargets,
            RemoteAnimationTargetCompat[] nonAppTargets,
            Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange) {
                RectF launcherIconBounds = new RectF();
                // 拿到launcher内定义的一个图片View,用于launcher自己的动画
                FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
                        !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */);
                ......
                // 后续触发leash动画更新的对象 ,这里的floatingView的桌面应用图标的动画View
                SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView);
                ......
                AnimatorSet animatorSet = new AnimatorSet();
                ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
                appAnimator.setDuration(APP_LAUNCH_DURATION);
                appAnimator.setInterpolator(LINEAR);
                appAnimator.addListener(floatingView);// launcher自己内部的动画
                appAnimator.addListener(new AnimatorListenerAdapter() {
                    ......
                });
                // 再定义一个动画的监听,这里会触发对leash的图层做动画
                // 这段是很长,但是也是重点,动画图层的update就在这
                MultiValueUpdateListener listener = new MultiValueUpdateListener() {
                    ......
                    // 重点* 
                    @Override
                    public void onUpdate(float percent, boolean initOnly) {
                        ......
                        // 定义需要对leash做动画的参数
                        ArrayList<SurfaceParams> params = new ArrayList<>();
                        // 遍历appTargets,里面有task要做动画的leash
                        for (int i = appTargets.length - 1; i >= 0; i--) {
                            // 重点* 根据leash构造动画参数
                            SurfaceParams.Builder builder = new SurfaceParams.Builder(.leash);
                            if (target.mode == MODE_OPENING) {
                                // 设置缩放比例    
                                matrix.setScale(scale, scale);
                                // 旋转角度
                                if (rotationChange == 1) {
                                    ......
                                } else if (rotationChange == 2) {
                                    ......
                                } else if (rotationChange == 3) {
                                    ......
                                } else {
                                    // 正常为0,走这
                                    matrix.postTranslate(windowTransX0, windowTransY0);
                                }

                                ......
                                // 设置动画参数
                                builder.withMatrix(matrix)
                                        .withWindowCrop(crop)
                                        .withAlpha(1f - mIconAlpha.value)
                                        .withCornerRadius(mWindowRadius.value)
                                        .withShadowRadius(mShadowRadius.value);
                                
                            } else if (target.mode == MODE_CLOSING) {
                                ......// 关闭的处理,类似上面,根据之前的分析,这个是对launcher的动画
                            }
                            // 执行build构建出SurfaceParams然后添加到集合
                            params.add(builder.build());
                        }
                        ......
                        // 开始应用动画
                        surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
                    }
                }  
                // 将上面构造的监听添加进去
                appAnimator.addUpdateListener(listener);  
                ......
                return animatorSet;  
            }

这段代码缩略后还是很长,但是主要做的事情比较简单,首先是创建了动画对象,其次是在 MultiValueUpdateListener::onUpdate 监听动画的更新。动画最重要的就是 onUpdate 了,可以看到这边是构造使用leash 图层构造了一个 SurfaceParams 对象,然后对 Matrix 透明度等一些属性的设置也都在这个对象里,然后再通过 SurfaceTransactionApplier::scheduleApply 方法来应用属性,达到动画目的。 那么对于 leash 图层也保存在 SurfaceParams 对象里了,当前动画时间段需要显示的属性也在 SurfaceParams 对象了,剩下的看怎么将去构建一个 Transaction ,然后把 leash 和参数都应用起来就可以了。 先看一下 SurfaceParams 的 Builder

# SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder
            final SurfaceControl surface;
            public Builder(SurfaceControl surface) {
                this.surface = surface;
            }

这里构建Builder的时候就会将leash保存在 surface 变量中,后面执行 build() 方法的时候就作为了 SurfaceParams 下的 surface 变量。

然后继续看下面流程:

# SurfaceTransactionApplier

    private final ViewRootImpl mTargetViewRootImpl;

    public void scheduleApply(final SurfaceParams... params) {
        // 拿到图标动画的View 
        View view = mTargetViewRootImpl.getView();
        if (view == null) {
            return;
        }
        // 构建一个Transaction最终执行动画图层的应用
        Transaction t = new Transaction();
        for (int i = params.length - 1; i >= 0; i--) {
            SurfaceParams surfaceParams = params[i];
            if (surfaceParams.surface.isValid()) {
                // 将前面构建的参数应用到Transaction中
                surfaceParams.applyTo(t);
            }
        }

        mLastSequenceNumber++;
        final int toApplySeqNo = mLastSequenceNumber;
        setCanRelease(false);
        // 注册回调,下一帧的时候执行动画的修改
        mTargetViewRootImpl.registerRtFrameCallback(frame -> {
            if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
                Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
                        .sendToTarget();
                return;
            }
            // 应用事务,真正让属性应用到leash图层中
            mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
            Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
                    .sendToTarget();
        });

        // Make sure a frame gets scheduled.
        view.invalidate();
    }

那真正要对一个 Surace 做改变,我们知道肯定是要通过 SurfaceControl.Transaction 来完成的。 所以这个方法就是创建一个 SurfaceControl.Transaction 对象,然后把之前构造的SurfaceParams 的参数应用到这个 Transaction 中,那么现在动画需要改变的属性都在这个 Transaction 里,只要有最后进行 Transaction::apply 就完成了。 这个方法的后面部分就对下一帧刷新做了监听,也就是说下一帧来的时候就会执行 ViewRootImpl::mergeWithNextTransaction ,那么内部肯定是会执行 Transaction::apply 的。

# ViewRootImpl

    public void mergeWithNextTransaction(Transaction t, long frameNumber) {
        if (mBlastBufferQueue != null) {
            mBlastBufferQueue.mergeWithNextTransaction(t, frameNumber);
        } else {
            // 执行apply
            t.apply();
        }
    }

到此,app_transition 的动画是怎么执行生效的就全部完成了,这里的逻辑比较简单,和普通写应用的动画差不多,在 onUpdate 里根据动画的时间,计算出当前需要显示的透明度,位置和大小,圆角等属性,然后应用到leash图层,以达到动画的效果。

剩下的就是看看动画结束的回调,是怎么传递到 system_server 进程了。

1.2 FloatingView

动画更新相关的代码很多地方提到了 FloatingView,这里只看一下FloatingView的构造方法知道他是做什么的。

/**
 * A view that is created to look like another view with the purpose of creating fluid animations.
 */
@TargetApi(Build.VERSION_CODES.Q)
public class FloatingIconView extends FrameLayout implements
        Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView {
            ......
        }

看上面的注释就是图标放大的动画,然后看来是Q版本才有这个动效。关于图标的动画是View动画,不在分析的范围内,后面有时间单独分析。

2. 动画结束回调

根据之前的代码,现在有2个动画结束回调的:

  1. WindowContainer 构造 SurfaceAnimator 时传递的 WindowContainer::onAnimationFinished
  2. RemoteAnimationController::goodToGo 开始触发 launcher 动画传递来的结束回调RemoteAnimationController::FinishedCallback ‘

那么现在需要弄明白动画结束回调执行的是哪个回调,和launcher进程是怎么触发结束回调的。

2.1 system_server 对结束回调的处理

虽然之前已经代码都提过结束回调,这一小节来重新单独整理一下: 首先是WindowContainer::onAnimationFinished这个回调被传递到SurfaceAnimator中

# SurfaceAnimator
    SurfaceAnimator(Animatable animatable,
            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
            WindowManagerService service) {
        mAnimatable = animatable;
        mService = service;
        mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;
        mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
    }
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
            ......
            mLeash = createAnimationLeash......
            mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
            ......
        }

在执行startAnimation方法的时候传递的是 mInnerAnimationFinishedCallback ,这个也就是经过一次封装的 WindowContainer::onAnimationFinished 然后下一步执行的是

# RemoteAnimationController.RemoteAnimationAdapterWrapper
         // 回调被保存在这个变量中
         private OnAnimationFinishedCallback mCapturedFinishCallback;
        // 回调就是现在的finishCallback参数
        public void startAnimation(SurfaceControl animationLeash, Transaction t,
                @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
                    ......
                    mCapturedLeash = animationLeash;
                    // 赋值给mCapturedFinishCallback
                    mCapturedFinishCallback = finishCallback;
                    mAnimationType = type;
                }

下一步就是 goodToGo 流程触发 launcher 执行动画

# RemoteAnimationController
    /
    private FinishedCallback mFinishedCallback;

    void goodToGo(@WindowManager.TransitionOldType int transit) {
        ......
        mFinishedCallback = new FinishedCallback(this);
        ......
        mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
                wallpaperTargets, nonAppTargets, mFinishedCallback);
    }

这个流程又重新创了个回调,然后传递给了 launcher ,那么 launcher 在动画结束后,肯定是调用的就是这个 FinishedCallback 了,所以先看看这里的 FinishedCallback 和最初的WindowContainer::onAnimationFinished 有什么关系.

# RemoteAnimationController
    // 是个binder
    private static final class FinishedCallback extends IRemoteAnimationFinishedCallback.Stub {
        // 外部类引用
        RemoteAnimationController mOuter;

        @Override
        public void onAnimationFinished() throws RemoteException {
            ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "app-onAnimationFinished(): mOuter=%s", mOuter);
            final long token = Binder.clearCallingIdentity();
            try {
                if (mOuter != null) {
                    mOuter.onAnimationFinished();

                    // In case the client holds on to the finish callback, make sure we don't leak
                    // RemoteAnimationController which in turn would leak the runner on the client.
                    mOuter = null;
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

    private void onAnimationFinished() {
        // 打印动画结束log
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "onAnimationFinished(): mPendingAnimations=%d",
                mPendingAnimations.size());
        ......
            try {
                // 再次打印log
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
                        "onAnimationFinished(): Notify animation finished:");

                for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
                    final RemoteAnimationRecord adapters = mPendingAnimations.get(i);
                    if (adapters.mAdapter != null) {
                        // 远端动画结束从这会调到最初的WindowContainer::onAnimationFinished
                        adapters.mAdapter.mCapturedFinishCallback
                                .onAnimationFinished(adapters.mAdapter.mAnimationType,
                                        adapters.mAdapter);
                    }
                    ......
                    // 远端动画只会执行一个,但是没执行的也会在这里一起触发完成回调
                }
                ......
            }......
        ......
    }

原来 RemoteAnimationController::FinishedCallback 内部调用的还是 WindowContainer::onAnimationFinished。 这里看到因为 launcher 执行完动画回调回来肯定是个跨进程通信,所以才再次将动画封装到 binder 类 FinishedCallback中。 那么剩下的就是看 launcher 进程是如何调用了。

2.2 launcher进程的调用

在launcher这边流程刚开始时也就是 LauncherAnimationRunner::onAnimationStart 就将回调放在了 AnimationResult 对象中,关于 AnimationResult 的构造方法如下:

# LauncherAnimationRunner.AnimationResult
        private final Runnable mASyncFinishRunnable;

        private AnimationResult(Runnable syncFinishRunnable, Runnable asyncFinishRunnable) {
            mSyncFinishRunnable = syncFinishRunnable;
            mASyncFinishRunnable = asyncFinishRunnable;
        }

也就是说动画结束回调被传递给了 mASyncFinishRunnable 变量。然后下一步就是 AppLaunchAnimationRunner::onAnimationStart 。

# QuickstepTransitionManager.AppLaunchAnimationRunner

        @Override
        public void onCreateAnimation(int transit,
                RemoteAnimationTargetCompat[] appTargets,
                RemoteAnimationTargetCompat[] wallpaperTargets,
                RemoteAnimationTargetCompat[] nonAppTargets,
                LauncherAnimationRunner.AnimationResult result) {
            // 创建个动画集合
            AnimatorSet anim = new AnimatorSet();
            ...... // 忽略从小部件或者recent打开的分支
            // 当前分支
            composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
                        launcherClosing);
            ......
            // 监听动画结束回调,执行 system_server 动画结束的回调
            result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,
                    skipFirstFrame);
        }

在这里 anim 经过 composeIconLaunchAnimator 方法后,里面已经有 leash 动画的 Animator 了。然后调用 AnimationResult::setAnimation。

# LauncherAnimationRunner.AnimationResult

        @UiThread
        public void setAnimation(AnimatorSet animation, Context context,
                @Nullable Runnable onCompleteCallback, boolean skipFirstFrame) {
                    ......
                    // leash 动画
                    mAnimator = animation;
                    ......
                    ......
                        // 设置动画结束监听
                        mAnimator.addListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                // leash动画结束,触发回调
                                finish();
                            }
                        });
                        mAnimator.start();
                        ......
                }
        
        @UiThread
        private void finish() {
            if (!mFinished) {
                mSyncFinishRunnable.run();
                UI_HELPER_EXECUTOR.execute(() -> {
                    // 重点* 远端动画结束,触发WMS回调
                    mASyncFinishRunnable.run();
                    if (mOnCompleteCallback != null) {
                        MAIN_EXECUTOR.execute(mOnCompleteCallback);
                    }
                });
                mFinished = true;
            }
        }

根据这段代码,AnimationResult 内会持有 leash 动画,然后设置动静结束监听,动画结束后执行回调,然后跨进程回调到 system_server 进程,最终触发构造 SurfaceAnimator 时传递的 WindowContainer::onAnimationFinished。