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

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

-- 服装学院的IT男

回顾一下 AppTransition 工作模型,在上一篇已经看完了 AppTransition 的2个重要的流程: prepareAppTransition ,executeAppTransition 并且知道 AppTransition::isReady 方法现在已经是返回 true 了。

本篇要继续介绍 AppTransition 的第三流程 :goodToGo 。这边也会看到很多和动画相关的代码。

我们知道要做动画就需要创建 leash 图层,搜索"animation-leash of"关键字最终确定是在 SurfaceAnimator::createAnimationLeash 方法完成对 leash 图层的创建,这个方法我加上堆栈后得到以下调用链:

php 复制代码
I WindowManager: Cancelling animation restarting=true for Task{fa3f494 #11 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
E biubiubiu: SurfaceAnimator  createAnimationLeash: app_transition
E biubiubiu: java.lang.Exception
E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2770)
E biubiubiu: 	at com.android.server.wm.WindowContainer$AnimationRunnerBuilder.lambda$build$4$com-android-server-wm-WindowContainer$AnimationRunnerBuilder(WindowContainer.java:3912)
E biubiubiu: 	at com.android.server.wm.WindowContainer$AnimationRunnerBuilder$$ExternalSyntheticLambda4.startAnimation(Unknown Source:7)
E biubiubiu: 	at com.android.server.wm.WindowContainer.applyAnimationUnchecked(WindowContainer.java:3073)
E biubiubiu: 	at com.android.server.wm.Task.applyAnimationUnchecked(Task.java:3380)
E biubiubiu: 	at com.android.server.wm.WindowContainer.applyAnimation(WindowContainer.java:2912)
E biubiubiu: 	at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:851)
E biubiubiu: 	at com.android.server.wm.AppTransitionController.applyAnimations(AppTransitionController.java:1024)
E biubiubiu: 	at com.android.server.wm.AppTransitionController.handleAppTransitionReady(AppTransitionController.java:284)
E biubiubiu: 	at com.android.server.wm.RootWindowContainer.checkAppTransitionReady(RootWindowContainer.java:979)
E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:846)
E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)

可以确定是在一次 layout 流程中,由 RootWindowContainer::checkAppTransitionReady 这个方法触发的。

1. RootWindowContainer::checkAppTransitionReady

scss 复制代码
# RootWindowContainer

    private void checkAppTransitionReady(WindowSurfacePlacer surfacePlacer) {
        // Trace all displays app transition by Z-order for pending layout change.
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            // 遍历所有的屏幕
            final DisplayContent curDisplay = mChildren.get(i);

            // If we are ready to perform an app transition, check through all of the app tokens
            // to be shown and see if they are ready to go.
            // 重点*1. 判断是否满足执行条件
            if (curDisplay.mAppTransition.isReady()) {
                // handleAppTransitionReady may modify curDisplay.pendingLayoutChanges.
                // 重点*2. 条件满足主要做的事 
                curDisplay.mAppTransitionController.handleAppTransitionReady();
                ......
            }
            ......
        }
    }

layout 流程是很频繁的,但是能不能进入后面的逻辑还是要看 AppTransition::isReady 条件是否满足,这个已经在上篇分析过了。

经过 1.3 小结,流程已经走到了AppTransition::setReady,那么对应的,本篇最上面 RootWindowContainer::checkAppTransitionReady 方法里的"mAppTransition.isReady()"就会返回true,则可以进入 AppTransitionController::handleAppTransitionReady ,这个方法主要的如下:

scss 复制代码
# AppTransitionController

    void handleAppTransitionReady() {
        mTempTransitionReasons.clear();
        // 重点* 1. 判断是否满足条件执行后续逻辑
        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
                || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
                || !transitionGoodToGoForTaskFragments()) {
            return;
        }
        // 经典日志输出
        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");

    	......
        // 根据debug当前案例这里返回的是12 表示 "TRANSIT_OLD_WALLPAPER_CLOSE",目前这个不影响动画的分析,暂不详细看这个方法
        @TransitionOldType final int transit = getTransitCompatType(
                mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
                mDisplayContent.mChangingContainers,
                mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
                mDisplayContent.mSkipAppTransitionAnimation);
        ......
        try {
        	// 重点* 2. 应用app transition动画
            applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
                    voiceInteraction);
            // 重点* 3. 处理closing activity可见性*/
            handleClosingApps();
            // 重点* 4.  处理opening actvity可见性*/
            handleOpeningApps();
            ......
            // 重点* 5. goodToGo
            layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
            ......
        } finally {
            mService.mSurfaceAnimationRunner.continueStartingAnimations();
        }
        ......
    }

这里有5个重点

    1. 想要继续执行后面的方法需要 transitionGoodToGo 返回true
    1. 执行动画相关的逻辑,主要是创建 leash 图层
    1. 处理需要 close 的 APP
    1. 处理需要 open 的 APP ,比如会设置窗口状态为 HAS_DRAWN
    1. goodToGo流程,也就是执行动画

这边主要关心 1,2,5 这3点和当前分析动画相关的逻辑。先看第一点

1.1 AppTransitionController::transitionGoodToGo

进入 AppTransitionController::handleAppTransitionReady 的条件是 AppTransition::isReady ,但是现在看来真正想执行这个方法的代码,还需要 transitionGoodToGo 这个方法返回true, 才行。

换句话说,只有在日志里看到下面这句打印的时候,才说明是真正执行了 AppTransitionController::handleAppTransitionReady 方法里面的内容

markdown 复制代码
V WindowManager: **** GOOD TO GO

所以先看一下 AppTransitionController::transitionGoodToGo 方法。

上次看到执行了3次,又一个返回 false,都会被 return 。这边只看传入"mDisplayContent.mOpeningApps"的条件即可。

java 复制代码
AppTransitionController
  private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
            ArrayMap<WindowContainer, Integer> outReasons) {
                // 关键日志
                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                        "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
                        mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
                // 这个一般不会出现
                if (mDisplayContent.mAppTransition.isTimeout()) {
                    return true;
                }
                ......// 省略屏幕旋转处理
                // 对每个APP处理
                for (int i = 0; i < apps.size(); i++) {
                    WindowContainer wc = apps.valueAt(i);
                    final ActivityRecord activity = getAppFromContainer(wc);
                    if (activity == null) {
                        continue;
                    }
                    // 关键日志,后面用来判断的几个值都在
                    ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                            "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                                    + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
                            activity, activity.allDrawn, activity.startingDisplayed,
                            activity.startingMoved, activity.isRelaunching(),
                            activity.mStartingWindow);
                    final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
                    if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
                        return false;
                    }
                    ......
                }
                ......//省略其他情况
                
                // 正常情况都在这里返回,如果壁纸不可见或壁纸过渡准备就绪,返回true,表示可以继续应用过渡
                return !mWallpaperControllerLocked.isWallpaperVisible()
                    || mWallpaperControllerLocked.wallpaperTransitionReady();
            }

这里3个 return 的地方,2个日志的打印。相关的解释已经在代码上了,这里2个对应打印的日志如下:

ini 复制代码
WindowManager: Checking 0 opening apps (frozen=false timeout=false)...

WindowManager: Check opening app=ActivityRecord{fea3a44 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t15}: allDrawn=false startingDisplayed=true startingMoved=false isRelaunching()=false startingWindow=Window{97dc59d u0 Splash Screen com.google.android.dialer}

这里既然已经打印了2个日志,那返回值肯定是在最后的那个 return 了。

根据目前的日志,这个方法肯定是返回 true 了的。以后这个返回 false 的可以根据这里的日志来判断是哪个变量导致的返回 false。一般来说需要关注的是 "activity.allDrawn" 这个值。

2 leash图层相关

前面包括上篇启动都是一些准备工作,现在要真正开始看动画相关的调用了。

java 复制代码
# AppTransitionController
    private void applyAnimations(ArraySet<ActivityRecord> openingApps,
            ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
            LayoutParams animLp, boolean voiceInteraction) {
                ......
                // 重点* 1. 提升打开动画层级对象,比如对整个Task做动画
                final ArraySet<WindowContainer> openingWcs = getAnimationTargets(
                    openingApps, closingApps, true /* visible */);
                // 提升关闭动画层级对象,比如对整个Task做动画
                final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
                    openingApps, closingApps, false /* visible */);
                // // 重点* 2. 可见的APP动画处理
                applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,
                    voiceInteraction);
                // 不可见的APP动画处理
                applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                    voiceInteraction);
                ......
            }

这里有4个方法的执行,但是是对打开和关闭的各自处理,其实也就是2对方法。 getAnimationTargets 和 applyAnimations 。

openingApps 就是 TargetActivity ,当前场景就是 "电话"这个应用的 Actyivity 。而 openingApps 就是 SourceActivity, 当前场景就是 launcher 。

2.1 提升动画目标图层

AppTransitionController::getAnimationTargets 这个方法是用来提升动画层级的。 怎么理解呢? 比如是应用的打开动画,按理说应该对窗口也就是 ActivityRecord 做动画。但是最终的 leash 图层是对整个 Task 做动画了。 这一步从 ActivityRecord --> Task 提升动画层级的,也就是找到合适的动画图层。 看一下这个方法的部分代码:

swift 复制代码
# AppTransitionController
    static ArraySet<WindowContainer> getAnimationTargets(
            ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
            boolean visible) {
                .......
                // 根据visible来判断源数据用哪个集合
                final ArraySet<ActivityRecord> apps = visible ? openingApps : closingApps;
                ......
                // The final animation targets which cannot promote to higher level anymore.
                final ArraySet<WindowContainer> targets = new ArraySet<>();
                ......
                // 打印经过处理的集合和源集合
                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "getAnimationTarget in=%s, out=%s",
                    apps, targets);
                // 返回经过提级后的容器列表
                return targets;
            }

这个方法很长,这里并没有完整代码,根据加的注释,这个方法就是返回一个动画集合的容器,比如当前场景传递进来的是"电话"的 ActivityRecord ,返回的是其 Task 。 这段的日志只看openingApps场景输入如下:

arduino 复制代码
10-25 20:04:39.900  1422  1492 V WindowManager: getAnimationTarget in={ActivityRecord{fea3a44 u0 com.google.android.dialer/.extensions.GoogleDialtactsActivity} t15}}, out={Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}}

具体传进来的是什么,提升后又是什么,感兴趣的可以自己去看这个方法,或者在不同场景看这个日志的打印。

2.2 创建leash图层

得到了提升后的目标动画图层,就会执行 AppTransitionController::applyAnimations 方法,开始创建 leash 图层了。

less 复制代码
# AppTransitionController 
    // 经过提级后,这里的wcs里面只有应用的Task,而apps还是应用的ActivityRecord
    private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
                // 当前场景只有1个
                final int wcsCount = wcs.size();

                for (int i = 0; i < wcsCount; i++) {
                    final WindowContainer wc = wcs.valueAt(i);
                    ......
                    //  遍历集合下的各个容器,触发动画逻辑
                    wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
                }
            }

# WindowContainer

    boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,
            boolean enter, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
                ......
                try {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
                    if (okToAnimate()) {
                        // 打印log
                        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                                "applyAnimation: transit=%s, enter=%b, wc=%s",
                                AppTransition.appTransitionOldToString(transit), enter, this);
                        // 主流程, 子类Task重写
                        applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
                    } else {
                        cancelAnimation();
                    }
                } finally {
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                }
                ......
            }

这段的日志输出如下:

arduino 复制代码
10-25 20:04:39.901  1422  1492 V WindowManager: applyAnimation: transit=TRANSIT_OLD_WALLPAPER_CLOSE, enter=true, wc=Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}

这里的 transit 为什么是这个前面提过了,继续看主流程。

less 复制代码
# WindowContainer

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
                // 默认返回null,Task重写返回Task本身,所以这里就是应用的Task
                final Task task = asTask();
                ......
                // 重点* 1 获取动画adapter
                final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
                        transit, enter, isVoiceInteraction);
                // 拿到动画的adapter
                AnimationAdapter adapter = adapters.first;
                // 缩略图的adapter,当前场景为null
                AnimationAdapter thumbnailAdapter = adapters.second;
                if (adapter != null) {
                    ......
                    AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
                    ......
                    // 重点* 2 主流程
                    animationRunnerBuilder.build()
                        .startAnimation(getPendingTransaction(), adapter, !isVisible(),
                                ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
                    ......
                }
            }

又看到了动画 Adapter 相关的东西了,肯定是和之前分析的 RemoteAnimationAdapter 要联系起来的。

2.2.1 获取动画 Adapter

谷歌对于动画的设计用的是 适配器模式,把具体场景的动画执行交给了对于的动画 Adapter 类。而当前的这个方法就是获取对于场景是由哪个 Adapter 来继续后续逻辑的。 而现在分析的启动动画,前面已经知道在 launcher 创建了一个 RemoteAnimationAdapter 并且已经传递到 system_service 进程了。 那现在又执行到这个方法,盲猜获取的 Adapter 就是 RemoteAnimationAdapter 。

继续看代码:

java 复制代码
# WindowContainer
    Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
            @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
            ......// 忽略其他场景
            // 1. 拿到controller
            final RemoteAnimationController controller =
                getDisplayContent().mAppTransition.getRemoteAnimationController();
            ......
            if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
                // 2. 通过controller创建RemoteAnimationRecord,isChanging为false,所以最后参数为null
                final RemoteAnimationController.RemoteAnimationRecord adapters =
                    controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
                            screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
                ......
                // debug看到 mThumbnailAdapter为null,需要留意其mAdapter
                resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
            } else ...... // 后面还有创建LocalAnimationAdapter的

         return resultAdapters;
    }

这里有2步:

    1. 先拿到 AppTransition 下的 mRemoteAnimationController 这个是前面提过的,里面有 launcher 传过来的 RemoteAnimationAdapter
    1. 通过 RemoteAnimationController 构建出一个 RemoteAnimationRecord 对象,然后将里面的对象放在 resultAdapters 中返回

那么问题的关键还是 RemoteAnimationController::createRemoteAnimationRecord 方法

csharp 复制代码
# RemoteAnimationController
    // 注意这个数组保存的是RemoteAnimationRecord
    private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();

    RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
            Point position, Rect localBounds, Rect endBounds, Rect startBounds) {

        // 日志,只是打印windowContainer,那肯定就是电话应用的 Task
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
                windowContainer);
        // 注意最后一个参数为null
        final RemoteAnimationRecord adapters = new  RemoteAnimationRecord(windowContainer, position,
                localBounds, endBounds, startBounds);
        // 加入到集合中
        mPendingAnimations.add(adapters);
        return adapters;
    }

# RemoteAnimationController.RemoteAnimationRecord
        RemoteAnimationAdapterWrapper mAdapter;
        
        RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
                Rect endBounds, Rect startBounds) {
            mWindowContainer = windowContainer;
            if (startBounds != null) {
                ......
            } else {
                // startBounds为null,走这
                mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
                        new Rect());
                mStartBounds = null;
            }
        }

这里因为 startBounds = null,走的是下面的逻辑,所以 mThumbnailAdapter 自然也就是null, 主要关注 mAdapter 变量,是个 RemoteAnimationAdapterWrapper 对象。 这里要注意,RemoteAnimationRecord 一旦创建就会被添加到 mPendingAnimations 集合中,用于后面googToGo流程创建需要传递到launcher进程做动画的应用目标 目前看到了 RemoteAnimationAdapterWrapper 的构建流程,知道 RemoteAnimationRecord 下面的mAdapter其实就是 RemoteAnimationAdapterWrapper

2.2.2 动画主流程

在 WindowContainer::applyAnimationUnchecked 看到首先是获取了动画 Adapter ,现在继续看主流程。 这里的 AnimationRunnerBuilder 是 WindowContainer 的内部类,所以调用的 startAnimation 方法其实是 WindowContainer.IAnimationStarter::build 下的方法。

最终也是调用的 WindowContainer::startAnimation

less 复制代码
# WindowContainer
    protected final SurfaceAnimator mSurfaceAnimator;

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim) {
        // 关键日志
        ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
                this, type, 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,
                animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
    }

根据前面的分析知道这里传递进来的 anim 就是 RemoteAnimationRecord 下的 mAdapter 也就是 RemoteAnimationAdapterWrapper ,这个的log打印也是这样

typescript 复制代码
10-25 20:04:39.901  1422  1492 V WindowManager: Starting animation on Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}: type=1, anim=com.android.server.wm.RemoteAnimationController$RemoteAnimationAdapterWrapper@a80500d

这个日志也是是很关键的。

2.2.2.1 SurfaceAnimator的创建及动画结束回调的设置

这里看到后续的流程是交给了 SurfaceAnimator 这个类,这个类是专门处理动画的,不管是远端动画还是本地动画都是通过其执行,而且动画结束的回调也是一样的。所以需要看一下这个 SurfaceAnimator 的定义。

mSurfaceAnimator 对象其实是在 WindowContainer 构造的时候赋值的。

scala 复制代码
# SurfaceAnimator
    // 其实也是WindowContainer
    final Animatable mAnimatable;
    // 动画结束回调
    final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
    // 再次封装的动画结束回调
    final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;

    SurfaceAnimator(Animatable animatable,
            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
            WindowManagerService service) {
        mAnimatable = animatable;
        mService = service;
        mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;
        mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
    }

# WindowContainer
    protected final WindowManagerService mWmService;

    //  WindowContainer实现了Animatable接口
    class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
            implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
            InsetsControlTarget {
                WindowContainer(WindowManagerService wms) {
                    mWmService = wms;
                    mTransitionController = mWmService.mAtmService.getTransitionController();
                    mPendingTransaction = wms.mTransactionFactory.get();
                    mSyncTransaction = wms.mTransactionFactory.get();
                    // 构建 SurfaceAnimator 传递了动画结束回调
                    mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
                    mSurfaceFreezer = new SurfaceFreezer(this, wms);
                }
        }

    protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
        // 处理动画结束的事情
        doAnimationFinished(type, anim);
        // 执行到 WindowManagerService
        mWmService.onAnimationFinished();
        mNeedsZBoost = false;
    }

SurfaceAnimator 下的 mAnimatable 就是 WindowContainer ,WindowContainer 构造的方法中会创建一个 SurfaceAnimator,也就是说每个 WindowContainer 都有一个 SurfaceAnimator 对象,然后将当前的onAnimationFinished方法作为动画结束回调传递进去。 也就是说动画结束的最终回调是执行到 WindowManagerService::onAnimationFinished 方法。

2.2.2.2 真正的创建leash图层

刚刚只是看了一下 SurfaceAnimator 构建以及动画回调。现在继续看主流程,SurfaceAnimator::startAnimation 方法。

less 复制代码
# SurfaceAnimator

    // anim 执行动画的adapter
    // type 动画类型
    // animationFinishedCallback 动画完成的回调
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
                cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
                // mAnimation的值是anim
                mAnimation = anim; 
                mAnimationType = type; 
                mSurfaceAnimationFinishedCallback = animationFinishedCallback;
                mAnimationCancelledCallback = animationCancelledCallback;
                // 拿到对应容器的SurfaceControl
                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;
                // 肯定为null
                if (mLeash == null) {
                    // 重点* 1. 创建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) {
                    ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
                    return;
                }
                // 重点*2. 开始动画,注意传进去的完成回调是经过处理的那个
                mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
                ......
            }

首先对需要关注的参数进行了注释,mAnimatable 的值是参数 anim ,那么也就是 RemoteAnimationAdapterWrappe 对象。 这里主要关注2件事:

    1. 创建动画图层
    1. 执行动画

2.2.3 createAnimationLeash -- 创建leash图层

arduino 复制代码
# SurfaceAnimator
    static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
            Transaction t, @AnimationType int type, int width, int height, int x, int y,
            boolean hidden, Supplier<Transaction> transactionFactory) {
        // 打印日志,
        ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
        final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
                .setParent(animatable.getAnimationLeashParent()) // 重点*1. 设置当前动画图层的父亲为需要做动画对象的父亲
                .setName(surface + " - animation-leash of " + animationTypeToString(type)) //设置名字
                .setHidden(hidden)
                .setEffectLayer()
                .setCallsite("SurfaceAnimator.createAnimationLeash");
        final SurfaceControl leash = builder.build(); // 构建出leash动画图层
        t.setWindowCrop(leash, width, height);
        t.setPosition(leash, x, y);
        t.show(leash);
        t.setAlpha(leash, hidden ? 0 : 1);

        t.reparent(surface, leash); //重点*2 . 将Task的父亲设置成leash
        return leash;
    }

重点分析:

    1. 这里设置了动画图层的父亲,根据上面的log和Winscope工具看到的,animatable就是启动"电话"创建的Task,那么这里就是将这个动画图层的父亲设置为了DefaultTaskDisplayArea
    1. 上一步操作完之后,task和leash的父亲都是DefaultTaskDisplayArea,然后这里再将task的父亲设置为leash,也就有了下面这个结构。也对应上了前面图片的开始动画的层级变化。

2.2.4 createAnimationLeash

根据之前的分析,直接去 RemoteAnimationAdapterWrappe 下看

less 复制代码
# RemoteAnimationController.RemoteAnimationAdapterWrapper

        SurfaceControl mCapturedLeash;
        private OnAnimationFinishedCallback mCapturedFinishCallback;

        @Override
        public void startAnimation(SurfaceControl animationLeash, Transaction t,
                @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
            // 打印关键日志
            ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
            // 当前案例mStartBounds为空
            if (mStartBounds.isEmpty()) {
                // Restore position and stack crop until client has a chance to modify it.
                t.setPosition(animationLeash, mPosition.x, mPosition.y);
                t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());
            } else {
                ......
            }
            // 将创建的leash图层保存
            mCapturedLeash = animationLeash;
            // 经过处理的回调保存
            mCapturedFinishCallback = finishCallback;
            mAnimationType = type;
        }

首先打印了一个"startAnimation"的 log ,这个也是一个关键的日志,日志中有这一步,说明远端动画的逻辑,在 system_service 进程已经处理好了。剩下的动画具体的执行就是在 launcher 进程 但是这里只是一些初始化,并没有像期望的那样真正的执行开始动画逻辑。 这就是适配器模式的特性,根据具体场景由对应的适配器做具体的业务处理,当前分析的远端动画,自然不会在这里直接开始动画,如果是本地动画,那本地动画的 LocalAnimationAdapter在这个方法就会真正的启动动画。

那么当前远端动画真正执行动画的逻辑在哪呢?在goodToGo!

2.3 调用链

这一块的调用链如下:

arduino 复制代码
RootWindowContainer::checkAppTransitionReady                 
    AppTransitionController::handleAppTransitionReady
        AppTransitionController::transitionGoodToGo
        AppTransitionController::applyAnimations
            AppTransitionController::applyAnimations
                WindowContainer::applyAnimation
                    Task::applyAnimationUnchecked
                        WindowContainer::applyAnimationUnchecked
                            WindowContainer::getAnimationAdapter
                                AppTransition.getRemoteAnimationController  -- 拿到 RemoteAnimationController
                                RemoteAnimationController::createRemoteAnimationRecord
                                    RemoteAnimationRecord::init
                                        mPendingAnimations::add -- 添加进集合
                            WindowContainer.AnimationRunnerBuilder::startAnimation -- 实际上是 WindowContainer.IAnimationStarter::build
                                WindowContainer::startAnimation
                                    SurfaceAnimator::startAnimation
                                        SurfaceAnimator::createAnimationLeash  --创建动画图层
                                        RemoteAnimationAdapterWrappe::startAnimation  --触发开始动画(实际上不是真正在开始)

3. goodToGo流程

现在可以正式来看AppTransitionController::handleAppTransitionReady方法处理的事情,整理如下: AppTransitionController::handleAppTransitionReady AppTransitionController::handleAppTransitionReady AppTransitionController::applyAnimations -- leash图层的创建 AppTransitionController::handleClosingApps AppTransitionController::handleOpeningApps --【Window各个状态流程--HAS_DRAWN】 AppTransition::goodToGo

前面跟踪 AppTransitionController::applyAnimations 后面的调用链看到了动画图层的创建 以及看到了通过 Adapter 启动动画(没有真的启动)。

现在继续接着看 AppTransitionController::handleAppTransitionReady 调用的另一个流程:AppTransition::goodToGo 流程,也是真正触发远端动画开始的地方。

less 复制代码
# AppTransition
    private RemoteAnimationController mRemoteAnimationController;

    int goodToGo(@TransitionOldType int transit, ActivityRecord topOpeningApp) {
        ......
        if (mRemoteAnimationController != null) {
            // 交给RemoteAnimationController执行goodToGo
            mRemoteAnimationController.goodToGo(transit);
        } else ......
    }

主要就是调用 RemoteAnimationController::goodToGo 来触发远端动画的执行。

less 复制代码
# RemoteAnimationController
    // 远端动画的Adapter
    private final RemoteAnimationAdapter mRemoteAnimationAdapter;

    void goodToGo(@WindowManager.TransitionOldType int transit) {
        // 打印goodToGo(),表现这才是真正的触发了goodToGo()逻辑
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
        ......
        // 重点* 1. 创建动画完成回调
        mFinishedCallback = new FinishedCallback(this);
        // 重点* 2. 创建launcher需要做远程动画的targets
        // Create the app targets
        final RemoteAnimationTarget[] appTargets = createAppAnimations();
        ......
        // 壁纸动画相关
        final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
        final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);
        mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
            try {
                linkToDeathOfRunner();
                // 打印日志,真正开始触发动画
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
                                + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
                        AppTransition.appTransitionOldToString(transit), appTargets.length,
                        wallpaperTargets.length, nonAppTargets.length);
                // 重点* 3. 这里是触发远端动画真正执行的地方
                // 注意传递过去的动画结束回调是 FinishedCallback
                mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
                        wallpaperTargets, nonAppTargets, mFinishedCallback);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to start remote animation", e);
                onAnimationFinished();
            }
            // 日志处理
            if (ProtoLogImpl.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
                writeStartDebugStatement();
            }
        });
        setRunningRemoteAnimation(true);
    }

在【动画-app_transition-1】中提到 system_service 会将要做动画的目标包装在一个 RemoteAnimationTarget 数组传递到 launcher 进程做动画,处理的地方就在这个方法里。 这里的3个数组,分别:appTargets ,wallpaperTargets ,nonAppTargets,分别代表应用目标,壁纸目标,非APP目标,我们只需要关注appTargets即可,这里的数组为2,分别代表"电话"应用的打开动画和launcher关闭的动画, wallpaperTargets 下自然就是壁纸的那根窗口。

这个代码会输出2个日志:

css 复制代码
10-25 20:04:39.905  1422  1492 D WindowManager: goodToGo()
10-25 20:04:39.911  1422  1492 D WindowManager: goodToGo(): onAnimationStart, transit=TRANSIT_OLD_WALLPAPER_CLOSE, apps=2, wallpapers=1, nonApps=0

这里的"goodToGo()" log 也是经典log之一,然后输出3个 target 的大小,壁纸 target 的为1,说明也要做动画(实际上没看到使用)。 那么这个方法主要处理了2件事:

    1. appTargets的数组处理
    1. 触发远端动画执行,这里的 mRemoteAnimationAdapter 就是 launcher 传递过来的那个

这里壁纸也有目标,说明也要做动画,根据 Winscope 的分析壁纸确实有一个 window_animation 的动画,但是在 WallpaperAnimationAdapter 这个专门给壁纸做动画的 Adapter 并没有看到真正的动画执行,然后这边也看到其作为 wallpaperTargets 也传递到 launcher 了,但是最后也没发现有做动画的地方。这点就很奇怪,不过在 Winscope 也只是看到 有 window_animation 的图层,但是也没看到有相关数值的改变,所以个人觉得壁纸的 window_animation 可能并没有什么实际的动画。

3.1 获取需要在远端知道动画的目标图层 createAppAnimations

java 复制代码
# RemoteAnimationController
    
    private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();

    private RemoteAnimationTarget[] createAppAnimations() {
        // log打印
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAppAnimations()");
        // 定义返回的集合
        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
        // 重点* 1. 遍历RemoteAnimationRecord集合
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
            // 之前存进来的RemoteAnimationRecord
            final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
            //  重点* 2. 调用RemoteAnimationTarget创建RemoteAnimationTarget
            final RemoteAnimationTarget target = wrappers.createRemoteAnimationTarget();
            // 如果创建成功则将RemoteAnimationTarget添加到需要返回的targets集合中,并打印日志
            if (target != null) {
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tAdd container=%s",
                        wrappers.mWindowContainer);
                targets.add(target);
            } else {
                ......// 忽略为null情况
            }
        }
        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
    }

这里如果有数据被add都有日志,如下:

arduino 复制代码
WindowManager: 	Add container=Task{9339170 #1 type=home ?? U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
WindowManager: 	Add container=Task{902e462 #15 type=standard A=10140:com.google.android.dialer U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}

所以确实是2个,TaskID = 1的是 launcher 的,我们主要关心的是应用进入的动画目标。

这里有2个需要关注点:

  1. 遍历的 mPendingAnimations 是什么? mPendingAnimations 在 RemoteAnimationController::getAnimationAdapter 流程的时候提过,通过 RemoteAnimationController::createRemoteAnimationRecord 构建出RemoteAnimationRecord 的时候就会添加到 mPendingAnimations 集合中,就是等这个时候goodToGo用。

  2. createRemoteAnimationTarget 方法

ini 复制代码
# RemoteAnimationController.RemoteAnimationRecord
        RemoteAnimationAdapterWrapper mAdapter;

        RemoteAnimationTarget createRemoteAnimationTarget() {
            // 先做条件判断,创建leash的流程提过,都满足
            if (mAdapter == null
                    || mAdapter.mCapturedFinishCallback == null
                    || mAdapter.mCapturedLeash == null) {
                return null;
            }
            // 由容器创建真正的RemoteAnimationTarget返回
            mTarget = mWindowContainer.createRemoteAnimationTarget(this);
            return mTarget;
        }

这里的 mAdapter 已经很熟悉了,判断条件的几个值也是在创建完 leash 图层后,执行其 startAnimation 方法初始化的时候赋值了的,再次提一下,mCapturedLeash 就是创建的leash图层。 WindowContainer::createRemoteAnimationTarget ,具体的在子类实现,那么当前场景就是在 TaskFragment。

scss 复制代码
# TaskFragment
    @Override
    RemoteAnimationTarget createRemoteAnimationTarget(
            RemoteAnimationController.RemoteAnimationRecord record) {
        android.util.Log.d("biubiubiu", "TaskFragment  createRemoteAnimationTarget: "+record.getMode());
        // MODE_OPENING值为0
        final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
                // There may be a trampoline activity without window on top of the existing task
                // which is moving to front. Exclude the finishing activity so the window of next
                // activity can be chosen to create the animation target.
                ? getTopNonFinishingActivity()
                : getTopMostActivity();
        return activity != null ? activity.createRemoteAnimationTarget(record) : null;
    }
# ActivityRecord
    @Override
    RemoteAnimationTarget createRemoteAnimationTarget(
            RemoteAnimationController.RemoteAnimationRecord record) {
        final WindowState mainWindow = findMainWindow();
        if (task == null || mainWindow == null) {
            return null;
        }
        final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
                task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect();
        InsetUtils.addInsets(insets, getLetterboxInsets());
        // 重点* 构造RemoteAnimationTarget,注意传递了leash图层
        final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId,
                record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(),
                new Rect(), insets,
                getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
                record.mAdapter.mEndBounds, task.getWindowConfiguration(),
                false /*isNotInRecents*/,
                record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
                record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
        target.hasAnimatingParent = record.hasAnimatingParent();
        return target;
    }

这里看到真正目标的创建是 ActivityRecord::createRemoteAnimationTarget 方法,这个方法真正创建了 RemoteAnimationTarget ,参数很多,主要看到传递了task的ID,和record.mAdapter.mCapturedLeash,这个指定是创建的动画图层,其他几个参数看到了再说吧。

到这里就知道了 RemoteAnimationTarget 是怎么创建的,以及知道其内部持有动画的 taskID 和做动画的动画图层。

3.2 触发launcher做动画

当前 system_service 的事情就告一段落了,后续的流程都是在launcher进程开始真正的执行动画。 也就是 RemoteAnimationController::goodToGo 方法调用的那句

scss 复制代码
mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,wallpaperTargets, nonAppTargets, mFinishedCallback);

这里的 mRemoteAnimationAdapter 以及 getRunner 之前都已经分析后了。

arduino 复制代码
AppTransition::goodToGo   --真正触发launcher进程开始动画
    RemoteAnimationController::goodToGo    --处理需要传递到launcher做动画的目标
        RemoteAnimationController::createAppAnimations
            mPendingAnimations::get  -- 取出RemoteAnimationRecord
            RemoteAnimationRecord::createRemoteAnimationTarget
                TaskFragment::createRemoteAnimationTarget
                    ActivityRecord::createRemoteAnimationTarget
        IRemoteAnimationRunner.Stub::onAnimationStart  --触发launcher真正做动画

4. 总结

本篇主要介绍了应用的 app_transition(打开)动画的 leash 图层是如何创建的,launcher 的app_transition(关闭)动画和本篇介绍的其实是一样的,流程,只不过是在 AppTransitionController::applyAnimations 多调用了一次 applyAnimations 方法,专递的参数为 false 罢了。

然后也提到了壁纸的 window_animation 动画。

另外也介绍了传递到 launcher做动画的3个 RemoteAnimationTarget 数组是怎么构建的。

本篇最后就是system_service 进程带着这些参数调用 launcher 的 RemoteAnimationAdapter 下 mRunner 对象的 onAnimationStart 方法开触发 launcher 进程做真正的动画播放处理。

本篇完整的调用链如下:

arduino 复制代码
RootWindowContainer::checkAppTransitionReady                 
    AppTransitionController::handleAppTransitionReady
        AppTransitionController::transitionGoodToGo
        AppTransitionController::applyAnimations
            AppTransitionController::applyAnimations  --(app_transition(打开))
                WindowContainer::applyAnimation
                    Task::applyAnimationUnchecked
                        WindowContainer::applyAnimationUnchecked
                            RemoteAnimationController::getAnimationAdapter    -- adapter相关处理
                                AppTransition.getRemoteAnimationController  -- 拿到 RemoteAnimationController
                                RemoteAnimationController::createRemoteAnimationRecord    
                                    RemoteAnimationController.RemoteAnimationRecord::init
                                        RemoteAnimationAdapterWrapper::init
                                            mPendingAnimations::add -- 添加RemoteAnimationRecord进集合
                            WindowContainer.AnimationRunnerBuilder::startAnimation --实际上是WindowContainer$IAnimationStarter::build方法下
                                WindowContainer::startAnimation
                                    SurfaceAnimator::startAnimation
                                        SurfaceAnimator::createAnimationLeash  --创建动画图层
                                        RemoteAnimationController.RemoteAnimationAdapterWrapper::startAnimation  --触发开始动画(实际上不是真正在开始)
                AppTransitionController::applyAnimations (app_transition(关闭))
    AppTransition::goodToGo   --真正触发launcher进程开始动画
        RemoteAnimationController::goodToGo    --处理需要传递到launcher做动画的目标
            RemoteAnimationController::createAppAnimations
                mPendingAnimations::get  -- 取出RemoteAnimationRecord
                RemoteAnimationRecord::createRemoteAnimationTarget
                    TaskFragment::createRemoteAnimationTarget
                        ActivityRecord::createRemoteAnimationTarget
            IRemoteAnimationRunner.Stub::onAnimationStart  --触发launcher真正做动画
相关推荐
长亭外的少年7 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿10 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神11 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛11 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法11 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter13 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快14 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl14 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江14 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-15 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记