Android T 远程动画显示流程其二——动画的添加流程(更新中)

时间:2024-02-22 17:51:28

前言

接着上篇文章分析
Android T 远程动画显示流程其一

切入点——处理应用的显示过渡

下面,我们以从桌面点击一个应用启动的场景来分析远程动画的流程,窗口添加的流程见Android T WMS窗口相关流程
这里我们从AppTransitionController.handleAppTransitionReady方法开始跟踪代码流程

代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java

    /**
     * Handle application transition for given display.
     */
    void handleAppTransitionReady() {
        ......
        //通过getTransitCompatType方法获取transit的值
        @TransitionOldType final int transit = getTransitCompatType(
                mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
                mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
                mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
                mDisplayContent.mSkipAppTransitionAnimation);
    	......
    	//方法收集正在打开 (mOpeningApps)、关闭 (mClosingApps) 和切换 (mChangingContainers) 的应用的activity类型
    	//并将它们存储在 activityTypes 集合中。
    	final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
                mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
        //被用于查找与给定transit和activityTypes相关的 ActivityRecord
        //也就是我们当前打开的应用的ActivityRecord
        final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
                mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                mDisplayContent.mChangingContainers);
        //获取正在打开的应用列表 (mOpeningApps) 中的顶层应用。
        //ignoreHidden 参数设置为 false,意味着即使应用是隐藏的,也会被考虑在内
        final ActivityRecord topOpeningApp =
                getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
        //获取正在关闭的应用列表 (mClosingApps) 中的顶层应用
        final ActivityRecord topClosingApp =
                getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
        //获取正在切换的应用列表 (mChangingContainers) 中的顶层应用
        //其取决于参数DisplayContent.mChangingContainers中是否有值
        /** 
        有三种情况会给DisplayContent.mChangingContainers中添加值
        1.{@link Task}在全屏和浮窗之间发生切换
        2.{@link TaskFragment}已组织好并且正在更改窗口边界
        3.{@link ActivityRecord}被重新分配到一个有组织的{@link TaskFragment}中
        **/
        final ActivityRecord topChangingApp =
                getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
        //从之前找到的animLpActivity(正在打开的应用的ActivityRecord)的窗口中获取布局参数
        final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
    	......
        try {
            /*1.1应用app transition动画(远程动画)*/
            applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
                    animLp, voiceInteraction);
            /*1.2.1处理closing activity可见性*/
            handleClosingApps();
            /*1.2.2处理opening actvity可见性*/
            handleOpeningApps();
            //处理用于处理正在切换的应用
            handleChangingApps(transit);
            //处理正在关闭或更改的容器
            handleClosingChangingContainers();

            //设置与最后一次应用过渡动画相关的信息
            appTransition.setLastAppTransition(transit, topOpeningApp,
                    topClosingApp, topChangingApp);

            final int flags = appTransition.getTransitFlags();
            /*1.3播放远程动画*/
            layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
            //处理非应用窗口的过渡动画
            handleNonAppWindowsInTransition(transit, flags);
            //执行动画回调
            appTransition.postAnimationCallback()
        } finally {
            mService.mSurfaceAnimationRunner.continueStartingAnimations();
        }
        ......

        // This has changed the visibility of windows, so perform
        // a new layout to get them all up-to-date.
        /*2.由于activity的可见性变更,将DisplayContent.mLayoutNeeded标志位置为true*/
        mDisplayContent.setLayoutNeeded();
        ......
    }

这个方法主要处理这三件事:
1.处理activity的过渡动画(远程动画)
2.分别调用 handleClosingApps以及handleOpeningApps对要关闭的和要打开的Activity进行可见性更新。
3.调用AppTransition.goodToGo方法走播放远程动画流程。
4.由于activity的可见性变更,将DisplayContent.mLayoutNeeded设置为true,该标志位在DisplayContent.performLayoutNoTrace中用来判断是否对当前DisplayContent下的所有窗口进行刷新。
这里我们主要关注远程动画的流程,主要分为两个部分。

  1. 处理并创建远程动画流程
    applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);
  2. 播放显示远程动画流程
    layoutRedo = appTransition.goodToGo(transit, topOpeningApp);

动画创建流程代码分析

applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);

基于一组ActivityRecord来应用动画,这些ActivityRecord表示正在进行切换的应用。
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps这两个参数分别代表正在打开和关闭的应用;
transit通过前面getTransitCompatType方法中获取,是TRANSIT_OLD_WALLPAPER_CLOSE(12);
animLp通过前面getAnimLp方法中获取,用于定义窗口的布局参数。这里就是代表正在打开的应用的ActivityRecord的窗口布局参数;
voiceInteraction:表示是否为语音交互。

处理并创建远程动画

代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java

    /**
     * Apply an app transition animation based on a set of {@link ActivityRecord}
     *
     * @param openingApps The list of opening apps to which an app transition animation applies.
     * @param closingApps The list of closing apps to which an app transition animation applies.
     * @param transit The current transition type.
     * @param animLp Layout parameters in which an app transition animation runs.
     * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
     *                         interaction session driving task.
     */
    private void applyAnimations(ArraySet<ActivityRecord> openingApps,
            ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
            LayoutParams animLp, boolean voiceInteraction) {
        //方法检查过渡类型是否未设置,或者打开和关闭的应用程序是否都为空。如果是,则方法直接返回,不执行任何动画。
        if (transit == WindowManager.TRANSIT_OLD_UNSET
                || (openingApps.isEmpty() && closingApps.isEmpty())) {
            return;
        }

        //调用getAnimationTargets方法获取打开和关闭的应用的窗口容器(WindowContainer)
        final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
                openingApps, closingApps, true /* visible */);
        final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
                openingApps, closingApps, false /* visible */);
        //打开和关闭的窗口应用动画。这是通过调重载的applyAnimations方法完成的,传递相应的参数,如动画的目标、过渡类型等。
        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
                voiceInteraction);
        applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                voiceInteraction);
        //如果存在最近任务动画控制器(RecentsAnimationController),则发送任务出现任务
        final RecentsAnimationController rac = mService.getRecentsAnimationController();
        if (rac != null) {
            rac.sendTasksAppeared();
        }

        //遍历打开和关闭的应用,并设置mOverrideTaskTransition为false
        for (int i = 0; i < openingApps.size(); ++i) {
            openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
        }
        for (int i = 0; i < closingApps.size(); ++i) {
            closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
        }

        //如果存在辅助功能控制器(AccessibilityController)且有回调,则调用其onAppWindowTransition方法。
        final AccessibilityController accessibilityController =
                mDisplayContent.mWmService.mAccessibilityController;
        if (accessibilityController.hasCallbacks()) {
            accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
        }
    }

传递关键参数,处理应用程序窗口的打开和关闭动画。
通过getAnimationTargets方法获取当前打开和关闭的应用的容器,即ActivityRecord的容器。
最关键的方法是调用的applyAnimations方法:

        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
                voiceInteraction);
        applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                voiceInteraction);

我们这里openingWcsclosingWcs实际上表示的是应用的容器,即Task;openingAppsclosingApps就是前面传递的mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,分别代表正在打开和关闭的应用,也是挂在对应Task下面的ActivityRecord。并且传递了应用的可见性visible,true可见,false不可见。
因此在我们桌面点击打开应用的流程中,openingWcs实际上指的是应用的Task,openingApps是应用的ActivityRecord(其实就是应用的主界面),其可见性为true;closingWcs对应的是桌面的Task,closingApps是桌面的ActivityRecord,其可见性为false。

这也对应了我们前面创建动画图层的堆栈中所打印的,先创建了应用的动画图层,后创建桌面的动画图层。

注:
从这里开始后续流程执行了两次,第一次是打开的应用流程,第二次是关闭的应用流程(一个应用的启动,伴随这另一个应用的退出,浮窗等特殊场景除外)。
从桌面点击开启应用的场景来说,一次是启动的应用角度执行流程,另一次是桌面角度执行流程。
从代码逻辑上来说,唯一的不同点是传递的可见性的值不同。

这个方法调用的是重载的applyAnimations方法

获取需要做动画的容器

	  /**
     * Apply animation to the set of window containers.
     *
     * @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.
     * @param apps The list of {@link ActivityRecord}s being transitioning.
     * @param transit The current transition type.
     * @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes
     *                invisible.
     * @param animLp Layout parameters in which an app transition animation runs.
     * @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice
     *                         interaction session driving task.
     */
    private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
        //获取窗口容器的数量
        final int wcsCount = wcs.size();
        //遍历每一个应用的窗口容器
        for (int i = 0; i < wcsCount; i++) {
            final WindowContainer wc = wcs.valueAt(i);
            // If app transition animation target is promoted to higher level, SurfaceAnimator
            // triggers WC#onAnimationFinished only on the promoted target. So we need to take care
            // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the
            // app transition.
            //对于每一个应用的窗口容器,检查正在进行切换的应用(apps)中哪些是该窗口容器的后代。
            //就比如应用的ActivityRecord是是应用的Task的后代
            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
            for (int j = 0; j < apps.size(); ++j) {
                final ActivityRecord app = apps.valueAt(j);
                //app如果是wc的后代,将其添加到一个列表中。
                if (app.isDescendantOf(wc)) {
                    transitioningDescendants.add(app);
                }
            }
            //调用每个应用的窗口容器的applyAnimation方法,传入相应的参数
            //这些参数包含动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。
            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
        }
    }

入参含义:
wcs: 一个WindowContainer对象的集合,这些对象是需要应用动画的窗口容器。
apps: 一个ActivityRecord对象的集合,这些对象表示正在进行切换的应用程序。
transit: 当前的过渡类型,例如淡入淡出、滑动等。
visible: 一个布尔值,表示应用是否变为可见。
animLp: 布局参数,定义了动画运行时的布局。
voiceInteraction: 一个布尔值,表示是否有语音交互。

关键代码解读:

  • final WindowContainer wc = wcs.valueAt(i);获取窗口容器
    wcs是前面传递过来的是Task,wc就是依次获取当前应用的Task和桌面Task。

  • transitioningDescendants存储的就是需要做动画的ActivityRecord。

  • 传递动画参数
    通过wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);方法,传递参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。
    wc就是Task,其没有applyAnimation方法,向上找父类WindowContainer.applyAnimation方法调用。

判断是否应用动画,传递相关参数

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

/**
     * Applies the app transition animation according the given the layout properties in the
     * window hierarchy.
     *
     * @param lp The layout parameters of the window.
     * @param transit The app transition type indicates what kind of transition to be applied.
     * @param enter Whether the app transition is entering transition or not.
     * @param isVoiceInteraction Whether the container is participating in voice interaction or not.
     * @param sources {@link ActivityRecord}s which causes this app transition animation.
     *
     * @return {@code true} when the container applied the app transition, {@code false} if the
     *         app transition is disabled or skipped.
     *
     * @see #getAnimationAdapter
     */
    boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
            boolean enter, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
        //判断是否禁用过渡动画
        if (mWmService.mDisableTransitionAnimation) {
            ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                    "applyAnimation: transition animation is disabled or skipped. "
                            + "container=%s", this);
            //取消当前动画
            cancelAnimation();
            return false;
        }

        // Only apply an animation if the display isn't frozen. If it is frozen, there is no reason
        // to animate and it can cause strange artifacts when we unfreeze the display if some
        // different animation is running.
        try {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
            //会判断是否有冻结,屏幕是否开启
            if (okToAnimate()) {
                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                        "applyAnimation: transit=%s, enter=%b, wc=%s",
                        AppTransition.appTransitionOldToString(transit), enter, this);
                //传递相关参数,创建AnimationAdapter和AnimationRunnerBuilder,准备启动动画
                applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
            } else {
                //取消当前动画
                cancelAnimation();
            }
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        //检查指定的窗口容器是否正在进行动画
        return isAnimating();
    }

下面说说里面的几个关键点:

  • 判断是否禁用过渡动画
    mWmService.mDisableTransitionAnimation
    这个变量是在WindowManagerService的构造方法中初始化的

    mDisableTransitionAnimation = context.getResources().getBoolean(
             com.android.internal.R.bool.config_disableTransitionAnimation);
    

    可以发现是读取config_disableTransitionAnimation配置项
    代码路径:frameworks/base/core/res/res/values/symbols.xml

     <java-symbol type="bool" name="config_disableTransitionAnimation" />
    

    定义了这个symbol
    代码路径:frameworks/base/core/res/res/values/config.xml

        <!-- Flag to disable all transition animations -->
        <bool name="config_disableTransitionAnimation">false</bool>
    

    定义了默认值为false,不禁用过渡动画

  • 取消当前动画
    cancelAnimation();

        void cancelAnimation() {
            //处理动画结束时的一些后续操作
            doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());
            //调用SurfaceAnimator.cancelAnimation方法来取消当前正在进行的动画
            mSurfaceAnimator.cancelAnimation();
            //调用unfreeze方法解除对显示的冻结状态,允许显示继续正常更新和渲染
            mSurfaceFreezer.unfreeze(getSyncTransaction());
        }
    

    doAnimationFinished方法在动画播放结束时处理回调逻辑中会调用到,具体见后面【动画移除流程】。

  • 准备动画
    applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
    把前面传递的参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表,再次传递到applyAnimationUnchecked方法中。
    注意,这里调用的是Task中重写的applyAnimationUnchecked方法,而不是直接调用的WindowContainer中的applyAnimationUnchecked方法。
    因为我们前面是通过前面AppTransitionController.applyAnimations中wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);调用过来的,因此此时的this指针指的是变量wc,即应用对应的Task。
    后面细讲applyAnimationUnchecked方法。

  • 检查动画
    代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        final boolean isAnimating(int flags, int typesToCheck) {
            return getAnimatingContainer(flags, typesToCheck) != null;
        }
    

    flags 用于确定要检查的动画类型和范围。
    typesToCheck 用于确定哪些类型的动画需要检查。
    方法内部调用 getAnimatingContainer 方法来获取正在进行动画的窗口容器,并根据返回值判断是否存在符合条件和目标标志的动画。
    如果返回值为 true,则说明存在符合条件的动画;如果返回值为 false,则说明不存在符合条件的动画。

处理最近任务状态的动画

applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
其中参数enter代表的其实就应用的可见性,从前面AppTransitionController.applyAnimations方法中逐步传递过来值有两个

applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp, voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction);

启动的应用的可见性为true,桌面的可见性为false
代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

@Override
    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
        //获取RecentsAnimationController
        //只有在最近任务中,切换到另一个应用时才会创建
        final RecentsAnimationController control = mWmService.getRecentsAnimationController();
        //RecentsAnimationController不为空
        if (control != null) {
            // We let the transition to be controlled by RecentsAnimation, and callback task's
            // RemoteAnimationTarget for remote runner to animate.
            //应用可见性为true,且当前activity不是桌面或者最近任务
            if (enter && !isActivityTypeHomeOrRecents()) {
                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
                        "applyAnimationUnchecked, control: %s, task: %s, transit: %s",
                        control, asTask(), AppTransition.appTransitionOldToString(transit));
                //执行最近任务动画逻辑
                control.addTaskToTargets(this, (type, anim) -> {
                    for (int i = 0; i < sources.size(); ++i) {
                        sources.get(i).onAnimationFinished(type, anim);
                    }
                });
            }
        //判断是否有返回手势
        } else if (mBackGestureStarted) {
            // Cancel playing transitions if a back navigation animation is in progress.
            // This bit is set by {@link BackNavigationController} when a back gesture is started.
            // It is used as a one-off transition overwrite that is cleared when the back gesture
            // is committed and triggers a transition, or when the gesture is cancelled.
            //返回手势mBackGestureStarted标志位置为false
            mBackGestureStarted = false;
            //设置一个标志为true,表示应跳过应用的过渡动画。
            mDisplayContent.mSkipAppTransitionAnimation = true;
            ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);
        } else {
            //调用父类WindowContainer的applyAnimationUnchecked方法
            super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
        }
    }

只有在最近任务中,切换到另一个应用时才会创建RecentsAnimationController,因此control的值为空。如果不为空,应用可见性为true,且当前activity不是桌面或者最近任务,则会进入到最近任务的动画处理逻辑。
我们在操作过程中也没有返回手势,因此mBackGestureStarted为false。
所以调用了父类WindowContainer的applyAnimationUnchecked方法。

获取AnimationAdapter并创建动画图层

接着前面super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);进行分析
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
        //获取当前Task
        final Task task = asTask();
        //当前Task不为空,应用变为不可见状态,且应用Task不为桌面或者最近任务
        //可以理解为应用按home键回到桌面的场景
        if