【Android 13源码分析】应用窗口显示动画-starting_reveal

1,011 阅读15分钟

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

                        -- 服装学院的IT男

本文介绍桌面启动应用场景出现的第二部分的动画:“starting_reveal” 它是展示动画,这个动画的出现说明应用 Window 已经绘制好,需要展示给用户了。出现这个动画也表示需要移除 StartWindow 了,移除 StartWindowh 会有个 “window_transition”动画,也就是整个启动应用的第三个动画。这个在StartWindow-SplashScreen介绍介绍。

本篇分析的的是应用 Window 要显示的动画 “starting_reveal”, 这个动画结束后才是 StartWindow 的移除。

在SurfaceAnimator::createAnimationLeash打印的当前动画创建的堆栈信息如下:

10-21 22:46:29.378 27972 27993 E biubiubiu: SurfaceAnimator  createAnimationLeash: starting_reveal
10-21 22:46:29.378 27972 27993 E biubiubiu: java.lang.Exception
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.createAnimationLeash(SurfaceAnimator.java:458)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.SurfaceAnimator.startAnimation(SurfaceAnimator.java:184)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2770)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2777)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.startAnimation(WindowContainer.java:2783)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.TaskOrganizerController.applyStartingWindowAnimation(TaskOrganizerController.java:490)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.TaskOrganizerController.removeStartingWindow(TaskOrganizerController.java:546)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.StartingSurfaceController$StartingSurface.remove(StartingSurfaceController.java:267)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.lambda$removeStartingWindowAnimation$6(ActivityRecord.java:2774)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord$$ExternalSyntheticLambda9.run(Unknown Source:6)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.removeStartingWindowAnimation(ActivityRecord.java:2780)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.removeStartingWindow(ActivityRecord.java:2715)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.ActivityRecord.onFirstWindowDrawn(ActivityRecord.java:6438)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.performShowLocked(WindowState.java:4708)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked(WindowStateAnimator.java:282)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent.lambda$new$8$com-android-server-wm-DisplayContent(DisplayContent.java:995)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent$$ExternalSyntheticLambda14.accept(Unknown Source:4)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2642)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2632)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4976)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4820)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1629)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1646)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.DisplayContent.applySurfaceChangesTransaction(DisplayContent.java:4700)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.applySurfaceChangesTransaction(RootWindowContainer.java:1027)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacementNoTrace(RootWindowContainer.java:828)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.RootWindowContainer.performSurfacePlacement(RootWindowContainer.java:788)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacementLoop(WindowSurfacePlacer.java:178)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:126)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer.performSurfacePlacement(WindowSurfacePlacer.java:115)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.wm.WindowSurfacePlacer$Traverser.run(WindowSurfacePlacer.java:57)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Handler.handleCallback(Handler.java:942)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Handler.dispatchMessage(Handler.java:99)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Looper.loopOnce(Looper.java:210)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.Looper.loop(Looper.java:297)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at android.os.HandlerThread.run(HandlerThread.java:67)
10-21 22:46:29.378 27972 27993 E biubiubiu: 	at com.android.server.ServiceThread.run(ServiceThread.java:44)

整理后调用链如下:

RootWindowContainer::performSurfacePlacementNoTrace
    RootWindowContainer::applySurfaceChangesTransaction
        DisplayContent::applySurfaceChangesTransaction
            DisplayContent::mApplySurfaceChangesTransaction  
                WindowStateAnimator::commitFinishDrawingLocked   -- READY_TO_SHOW
                    WindowState::performShowLocked   -- HAS_DRAWN
                        ActivityRecord::onFirstWindowDrawn -- 移除StartWindow,开始starting_reveal动画流程 
                            ActivityRecord::removeStartingWindow
                                ActivityRecord::removeStartingWindowAnimation
                                    StartingSurfaceController.StartingSurface::remove
                                        TaskOrganizerController::removeStartingWindow
                                            TaskOrganizerController::applyStartingWindowAnimation -- leash图层相关
                                                WindowContainer::startAnimation
                                                     WindowContainer::startAnimation
                                                         WindowContainer::startAnimation
                                                            SurfaceAnimator::startAnimation   --创建leash图层
                                                                SurfaceAnimator.createAnimationLeash
                                                                StartingWindowAnimationAdaptor::startAnimation
                                            ITaskOrganizer.removeStartingWindow    -- 跨进程通知SystemUI处理( ShellTaskOrganizer 远端动画)

这个调用链看前面部分还是很熟悉的,就 finishDrawingWindow 流程。 在执行 WindowStateAnimator.commitFinishDrawingLocked 和 WindowState::performShowLocked 一次把应用窗口的状态设置成 READY_TO_SHOW 和 HAS_DRAWN ,也就意味着应用窗口绘制完成了,那这个时候就可以把 StartWindow 移除了。于是会触发 ActivityRecord::onFirstWindowDrawn 方法,之后的逻辑就是 StartWindow 的移除流程。

1. system_server 阶段

StartWindow 的移除动画是在 SystemUI 执行的,所以 system_server 阶段做的事是创建相应的动画图层,而不会触发动画的执行。

1.1 应用窗户绘制完成状态设置

从WindowStateAnimator::commitFinishDrawingLocked 开始看。

# WindowStateAnimator
    boolean commitFinishDrawingLocked() {
        ......
        // mWin的打印是自己加的
        ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s mWin= %s",
                mSurfaceController,mWin);
        // 重点* finishDrawing设置状态为READY_TO_SHOW
        mDrawState = READY_TO_SHOW;
        boolean result = false;
        final ActivityRecord activity = mWin.mActivityRecord;
        // 重点* 这里是条件canShowWindows满足,才会进入
        if (activity == null || activity.canShowWindows()
                || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
            result = mWin.performShowLocked();
        }
        return result;
    }

# ActivityRecord
    boolean canShowWindows() {
        // allDrawn 是关键,在DisplayContent::applySurfaceChangesTransaction执行满足条件会置位true
        return allDrawn && !(isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)
                && hasNonDefaultColorWindow());
    }

这里需要注意的是当前的 windowState 的应用的,这里进入后续的条件是“activity.canShowWindows()”返回true,那么继续看 WindowState 的处理

# WindowState
    boolean performShowLocked() {
        ......
        final int drawState = mWinAnimator.mDrawState;
        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
                //  remonve startWindow 流程
                mActivityRecord.onFirstWindowDrawn(this); 
            } else {
                mActivityRecord.onStartingWindowDrawn();
            }
        }
        // 判断当前状态是否满足
        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
            return false;
        }
        ......
        ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
        mWinAnimator.mDrawState = HAS_DRAWN;
        ......
    }

这个方法如果条件满足还会执行将窗口状态设置为 HAS_DRAWN ,不过之前分析过了当前主要看 remove startWindow 的流程,走的是 ActivityRecord::onFirstWindowDrawn 这里可能会有人会有误解:以为当前是 TYPE_APPLICATION_STARTING 类型应该走下面,这里要注意的是,执行当前调用链的windowState是 应用的 WindowState, 并不是 startWindow的WindowSate。

1.2 开始触发移除StartWindow

下面就是开始触发移除 StartWindow 的逻辑了。

# ActivityRecord
    void onFirstWindowDrawn(WindowState win) {
        firstWindowDrawn = true;
        ......
        final Task associatedTask =
                mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
        // 根据之前对add流程分析,这里的associatedTask就是null
        if (associatedTask == null) {
            // 触发removeStartingWindow
            removeStartingWindow();
        } else ......
    }

    void removeStartingWindow() {
        ......
        // 移除动画,参数为true
        removeStartingWindowAnimation(true /* prepareAnimation */);
        ......
    }

    void removeStartingWindowAnimation(boolean prepareAnimation) {
        ......
        // 定义局部变量
        final StartingSurfaceController.StartingSurface surface;
        // mStartingData在add流程的时候创建的
        final StartingData startingData = mStartingData;
        if (mStartingData != null) {
            // 赋值
            surface = mStartingSurface;
            // 然后mStartingData就设置为null
            mStartingData = null;
            ......
        } else ......
        // 打印日志
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
                + " startingView=%s Callers=%s", this, mStartingWindow, mStartingSurface,
                Debug.getCallers(5));
        // 这里主要看内部的执行就好
        final Runnable removeSurface = () -> {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);
            try {
                // 移除 startWindow
                surface.remove(prepareAnimation && startingData.needRevealAnimation());
            } catch (Exception e) {
                Slog.w(TAG_WM, "Exception when removing starting window", e);
            }
        };

        removeSurface.run();
    }

这里调用 StartingSurfaceController.StartingSurface::remove ,参数是个 bool 值, prepareAnimation 根据调用过来的知道是为 true ,另外一个变量,根据之前的分析知道 startingData 实际上是 SplashScreenStartingData ,而在 SplashScreenStartingData 中 needRevealAnimation 返回的是 true 。 这块值的代码如下:

# needRevealAnimation
    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
                activity, mTheme);
    }

    @Override
    boolean needRevealAnimation() {
        // 注意为true
        return true;
    }

那么知道参数为 true 后继续看 remove 方法。

# StartingSurfaceController.StartingSurface
        public void remove(boolean animate) {
            synchronized (mService.mGlobalLock) {
                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
            }
        }

这里的 Task 自然就是应用的 Task ,然后调用到 TaskOrganizerController::removeStartingWindow

# TaskOrganizerController

    void removeStartingWindow(Task task, boolean prepareAnimation) {
        ......
        // 稍后跨进程通信
        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
        if (lastOrganizer == null) {
            return;
        }
        // 构建一个StartingWindowRemovalInfo的对象,然后填充数据,携带leash图层跨进程执行动画
        final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
        removalInfo.taskId = task.mTaskId;
        removalInfo.playRevealAnimation = prepareAnimation
                && task.getDisplayInfo().state == Display.STATE_ON;
        final boolean playShiftUpAnimation = !task.inMultiWindowMode();
        // 拿到topActivity
        final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
        if (topActivity != null) {
            removalInfo.deferRemoveForIme = topActivity.mDisplayContent
                    .mayImeShowOnLaunchingActivity(topActivity);
            if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
                final WindowState mainWindow =
                        topActivity.findMainWindow(false/* includeStartingApp */);
                if (mainWindow != null) {
                    // 重点* 1. 创建starting_reveal动画
                    removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
                    removalInfo.mainFrame = mainWindow.getRelativeFrame();
                }
            }
        }
        try {
            // 重点* 2. 跨进程调用到systemUI 进程执行后续remove流程
            lastOrganizer.removeStartingWindow(removalInfo);
        } catch (RemoteException e) {
            Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
        }
    }

这个方法就比较重要了,做了2件事:

    1. 在当前进程(system_server)创建动画的leash图层,并且把这个leash 保存到 removalInfo 变量下,后续传递到了 SystemUI 进程做动画
    1. 跨进程通知 SystemUI 进程开始执行移除动画

1.3 创建leash图层

# TaskOrganizerController
    static SurfaceControl applyStartingWindowAnimation(WindowState window) {
        final SurfaceControl.Transaction t = window.getPendingTransaction();
        final Rect mainFrame = window.getRelativeFrame();
        // 注意adapter
        final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
        // 是activity的WindowState,注意这个 ANIMATION_TYPE_STARTING_REVEAL 创建的是start_reveal动画
        window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
        t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
        return adaptor.mAnimationLeash;
    }

创建 StartingWindowAnimationAdaptor 的时候,是没传参数的,但是后面看的调用了 adaptor.mAnimationLeash,那么这个 mAnimationLeash 的赋值肯定是在中间这句WindowState::startAnimation 的流程,根据之前的流程知道实现是在 WindowContainer 中

# WindowContainer
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type) {
        // 注意最后一个参数为null
        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
    }

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
        // 后面2个个参数为null
        startAnimation(t, anim, hidden, type, animationFinishedCallback,
                null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
    }
    // 调用到这,后面3个参数都为null
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim) {
        // 打印log
        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.
        // 倒数第二个参数传过来是null
        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
    }

这一段 WindowContainer::startAnimation 的调用链也是动画里很常见的。

通过几次重载,最后执行的是 7个函数的方法,其中后面3个参数传递是null。其实动画结束和取消的回调都是null,由此可以猜测在 system_server 进程对 StartWindow 动画结束的回调并不关系。

后面就是和之前 app_transition 动画一样调用到 SurfaceAnimator 了,这个方法和应用启动动画也是一个套路,创建动画图层,然后执行 adapter 的 startAnimation 。

# SurfaceAnimator

    private AnimationAdapter mAnimation;

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
                ......
                if (mLeash == null) {
                    mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                            mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                            0 /* y */, hidden, mService.mTransactionFactory);
                    mAnimatable.onAnimationLeashCreated(t, mLeash);
                }
                ......
                mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
                ......
            }

这里创建 leash 图层以前说过,但是需要注意2点:

    1. 这个时候的type是上面代码传递过来的 ANIMATION_TYPE_STARTING_REVEAL ,所以返回的动画类型是 “starting_reveal“
    1. 这里并没有执行提升动画层级的方法,所以执行 leash 图层的下级是 应用的 WindowState(根据 winscope 的信息也是如此)

根据前面的信息,这里的 mAnimation 是 StartingWindowAnimationAdaptor ,然后创建

# StartingWindowAnimationAdaptor
        public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
                int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
            mAnimationLeash = animationLeash;
        }

只做了一件事,将创建的 leash 赋值到 mAnimationLeash ,不过根据前面的调用也确实够了,要的就是这句,后面的流程就要进入 SystemUI 进程执行了。

2. SystemUI进程执行远端动画

在 system_server 进程执行 TaskOrganizerController::removeStartingWindow 方法时,会先触发leash图层相关的对象创建,然后一切都准备好了后,会执行 ITaskOrganizer::removeStartingWindow 来通知 SystemUI 进程执行远端动画。 在 SystemUI 进程的实现类是 ShellTaskOrganizer 。

这部分的调用链如下:

TaskOrganizer.mInterface::removeStartingWindow
    ShellTaskOrganizer::removeStartingWindow
        StartingSurfaceDrawer::removeStartingWindow
            StartingSurfaceDrawer::removeWindowSynced
                SplashscreenContentDrawer::applyExitAnimation
                    SplashScreenExitAnimation::startAnimations
                        SplashScreenExitAnimation::createAnimator  --创建动画
                            SplashScreenExitAnimation::onAnimationProgress  --动画更新
                            SplashScreenExitAnimation::onAnimationEnd   --动画结束
                                SplashScreenExitAnimation::reset        --触发动画结束回调,执行remove StartWindow流程
                                    StartingSurfaceDrawer::removeWindowInner
                        ValueAnimator::start   --开始动画
# TaskOrganizer
    private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
        @Override
        public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
            mExecutor.execute(() -> TaskOrganizer.this.removeStartingWindow(removalInfo));
        }
    }

实现在 ShellTaskOrganizer

# ShellTaskOrganizer

    private StartingWindowController mStartingWindow;

    @Override
    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
        if (mStartingWindow != null) {
            mStartingWindow.removeStartingWindow(removalInfo);
        }
    }

# StartingWindowController

    private final StartingSurfaceDrawer mStartingSurfaceDrawer;

    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
        mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
                removalInfo));
        ......
    }

后续流程在 StartingSurfaceDrawer::removeStartingWindow 下,这里也会打印 StartWindow 的一个日志。

# StartingSurfaceDrawer  

    public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
        // 重点* 日志
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                "Task start finish, remove starting surface for task: %d",
                removalInfo.taskId);
        // 第二个参数为false
        removeWindowSynced(removalInfo, false /* immediately */);
    }

    protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
        final int taskId = removalInfo.taskId;
        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
        if (record != null) {
            if (record.mDecorView != null) {
                // 重点 * 关键日志
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                        "Removing splash screen window for task: %d", taskId);

                if (record.mContentView != null) {
                    record.clearSystemBarColor();
                    if (immediately
                            || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                        removeWindowInner(record.mDecorView, false);
                    } else {
                        // 注意,immediately为false,进入的是这个逻辑,playRevealAnimation前面看到了是为true
                        if (removalInfo.playRevealAnimation) {
                            // 重点 * 执行退出动画,注意这几个参数
                            mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
                                    removalInfo.windowAnimationLeash, removalInfo.mainFrame,
                                    () -> removeWindowInner(record.mDecorView, true),
                                    record.mCreateTime);
                        } else {
                            removeWindowInner(record.mDecorView, true);
                        }
                    }
                } else {
                    // shouldn't happen
                    Slog.e(TAG, "Found empty splash screen, remove!");
                    removeWindowInner(record.mDecorView, false);
                }
            }
            ......
        }
    }

注意这里有2个 StartWindow 的关键日志打印,然后有4个地方进入 removeWindowInner 方法,但是当前逻辑走的是第二个,playRevealAnimation 为 true 的这个。

看 applyExitAnimation 方法之前需要注意这几个参数:

  • record.mContentView :这个就是 Splashscreen 的 ContentView
  • removalInfo.windowAnimationLeash : 是跨进程传递的对象,这个是 leash 图层
  • removeWindowInner(record.mDecorView, true) : 动画结束回调,内部移除 StartWindow ,也就是说在执行完 StartWindow 移除动画后才会移除 StartWindow 的窗口。
# SplashscreenContentDrawer

    void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
            Rect frame, Runnable finishCallback, long createTime) {

        // 构建了一个Runnable
        final Runnable playAnimation = () -> {
            // 注意,动画结束的回调传到了SplashScreenExitAnimation中
            final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
                    view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
            animation.startAnimations();
        };
        // 这个肯定是有值的。根据流程或者只根据会打印下面的log都可以确定
        if (view.getIconView() == null) {
            playAnimation.run();
            return;
        }
        final long appReadyDuration = SystemClock.uptimeMillis() - createTime;
        final long animDuration = view.getIconAnimationDuration() != null
                ? view.getIconAnimationDuration().toMillis() : 0;
        final long minimumShowingDuration = getShowingDuration(animDuration, appReadyDuration);
        final long delayed = minimumShowingDuration - appReadyDuration;
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                "applyExitAnimation delayed: %s", delayed);
        // 根据log 值为0,没有延迟,所以直接执行
        if (delayed > 0) {
            view.postDelayed(playAnimation, delayed);
        } else {
            playAnimation.run();
        }
    }

这里看到后面主要的逻辑都在 SplashScreenExitAnimation 中了, 这个类的名字也很明显。

2.2 动画核心类 - SplashScreenExitAnimation

先看构造方法

# SplashScreenExitAnimation
    // SplashScreenExitAnimation是一个动画监听
    public class SplashScreenExitAnimation implements Animator.AnimatorListener {
        private final SplashScreenView mSplashScreenView;
        // 动画的leash图层
        private final SurfaceControl mFirstWindowSurface;
        // 动画结束回调
        private Runnable mFinishCallback;

        // 构造方法
        SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
                Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
            mSplashScreenView = view;
            mFirstWindowSurface = leash;
            if (frame != null) {
                mFirstWindowFrame.set(frame);
            }
            // 拿到图标
            View iconView = view.getIconView();
            // 判断是否执行执行图标动画,以及一些参数的处理
            // If the icon and the background are invisible, don't animate it
            if (iconView == null || iconView.getLayoutParams().width == 0
                    || iconView.getLayoutParams().height == 0) {
                mIconFadeOutDuration = 0;
                mIconStartAlpha = 0;
                mBrandingStartAlpha = 0;
                mAppRevealDelay = 0;
            } else {
                iconView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                // The branding view could only exists when the icon is present.
                final View brandingView = view.getBrandingView();
                if (brandingView != null) {
                    mBrandingStartAlpha = brandingView.getAlpha();
                } else {
                    mBrandingStartAlpha = 0;
                }
                mIconFadeOutDuration = context.getResources().getInteger(
                        R.integer.starting_window_app_reveal_icon_fade_out_duration);
                mAppRevealDelay = context.getResources().getInteger(
                        R.integer.starting_window_app_reveal_anim_delay);
                mIconStartAlpha = iconView.getAlpha();
            }
            mAppRevealDuration = context.getResources().getInteger(
                    R.integer.starting_window_app_reveal_anim_duration);
            mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
            mMainWindowShiftLength = mainWindowShiftLength;
            //  动画结束回调
            mFinishCallback = handleFinish;
            mTransactionPool = pool;
        }
    }

构造方法里主要是对一些参数的获取初始化,为了后续的动画。有个印象即可,主要知道将动画结束的回调设置给了mFinishCallback。然后后开始动画SplashScreenExitAnimation::startAnimations

# SplashScreenExitAnimation

        private ValueAnimator mMainAnimator;

        void startAnimations() {
            // 创建并执行
            mMainAnimator = createAnimator();
            mMainAnimator.start();
        }

createAnimator会返回一个 ValueAnimator ,然后就 start 了,那么重点就是看具体的动画是怎么创建的了

# SplashScreenExitAnimation

    // 根据官方注释,这里的动画是:淡出图标,显示应用程序,上移主窗口
    // fade out icon, reveal app, shift up main window
    private ValueAnimator createAnimator() {
        // reveal app
        final float transparentRatio = 0.8f;
        final int globalHeight = mSplashScreenView.getHeight();
        final int verticalCircleCenter = 0;
        final int finalVerticalLength = globalHeight - verticalCircleCenter;
        final int halfWidth = mSplashScreenView.getWidth() / 2;
        final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
                Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
        final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
        final float[] stops = {0f, transparentRatio, 1f};

        mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
        mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
        mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
        mRadialVanishAnimation.setRadialPaintParam(colors, stops);

        if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
            // shift up main window
            View occludeHoleView = new View(mSplashScreenView.getContext());
            if (DEBUG_EXIT_ANIMATION_BLEND) {
                occludeHoleView.setBackgroundColor(Color.BLUE);
            } else {
                occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
            }
            final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                    WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
            mSplashScreenView.addView(occludeHoleView, params);

            mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
        }
        // 前面都是一些参数的准备,这里开始构建东相关
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
        animator.setDuration(mAnimationDuration);
        animator.setInterpolator(Interpolators.LINEAR);
        // 添加监听
        animator.addListener(this);
        // 添加动画更新监听,传递了当前的进度
        animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
        return animator;
    }

这里主要关注动画的 update ,执行的是当前类的 onAnimationProgress 方法

# SplashScreenExitAnimation

    private void onAnimationProgress(float linearProgress) {
        // 这里处理提吧的淡入淡出动画
        onFadeOutProgress(linearProgress);

        final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
                mAppRevealDuration);
        // 应用的进入
        if (mRadialVanishAnimation != null) {
            mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
        }
        // 主窗口的上移
        if (mShiftUpAnimation != null) {
            mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
        }
    }

到这里动画就算结束了。至于这3个动画具体的执行,一般来说都是不需要修改的,所以就不细看了,如果需要修改可以找到对应地方看。

2.2.1 图标淡入淡出

2.2.2 应用进入RadialVanishAnimation

2.2.3 主窗口的上移ShiftUpAnimation

3. 动画结束

之前知道动画结束的回调被保存在 SplashScreenExitAnimation 下的 mFinishCallback 变量中,SplashScreenExitAnimation 本身是个动画监听,所以看他的动画结束回调即可。

# SplashScreenExitAnimation
    @Override
    public void onAnimationEnd(Animator animation) {
        reset();
        InteractionJankMonitor.getInstance().end(CUJ_SPLASHSCREEN_EXIT_ANIM);
    }

这里主要看的是reset()方法,另外在当前类搜索 mFinishCallback 使用的地方就是在 reset() 方法中,reset() 又在 onAnimationEnd 和 onAnimationCancel 中调用,也就是动画结束或者取消都会执行。

# SplashScreenExitAnimation
    private void reset() {
        if (DEBUG_EXIT_ANIMATION) {
            Slog.v(TAG, "vanish animation finished");
        }
        // 检查 SplashScreenView 是否附加到窗口上
        if (mSplashScreenView.isAttachedToWindow()) {
            // 影藏
            mSplashScreenView.setVisibility(GONE);
            if (mFinishCallback != null) {
                // 执行回调
                mFinishCallback.run();
                mFinishCallback = null;
            }
        }
        if (mShiftUpAnimation != null) {
            // 结束位移动画
            mShiftUpAnimation.finish();
        }
    }

这里主要关注动画结束回调的执行,也就是触发了 StartingSurfaceDrawer::removeWindowInner 方法的执行

# StartingSurfaceDrawer
    private void removeWindowInner(View decorView, boolean hideView) {
        if (mSysuiProxy != null) {
            mSysuiProxy.requestTopUi(false, TAG);
        }
        if (hideView) {
            // 设置GONE,这里的decorView就是startWindow的decorView
            decorView.setVisibility(View.GONE);
        }
        // 触发remove
        mWindowManagerGlobal.removeView(decorView, false /* immediate */);
    }

这里主要是starting_reveal动画结束后会触发 startWindow 的移除。这个就和 【removeWindow】一样了。

4. 总结

本篇主要内容就是 starting_reveal 动画, 这个动画结束后,才会触发移除 StartWindow 的流程和动画。

    1. 触发时机是在应用窗口绘制完整,设置窗口状态为 HAS_DRAWN 时。
    1. 移除动画是远端动画,真正的执行在 SystemUI进程
    1. 动画结束后会才触发 startWindow 的移除