【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真正做动画
相关推荐
调皮的芋头1 小时前
iOS各个证书生成细节
人工智能·ios·app·aigc
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_5 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存7 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子7 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch11 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391915 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup