Android V app 冷启动(11) 移除启动窗口

375 阅读7分钟

目前,屏幕上显示的仍然是启动窗口,而真窗位于其下。那么,当真窗绘制完成后,是如何显示出来呢,从本文开始,将逐步揭晓。

真窗绘制完成

当真窗绘制完成,会上报 WMS,如下

// WindowManagerService.java

void finishDrawingWindow(Session session, IWindow client,
        @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
    if (postDrawTransaction != null) {
        postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
    }

    final long origId = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            // 获取对应的 WindowState
            WindowState win = windowForClientLocked(session, client, false);
            ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
                    win, (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                    
            // 1. 由 WindowState 处理 finish drawing
            if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
                if (win.hasWallpaper()) {
                    win.getDisplayContent().pendingLayoutChanges |=
                            WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                }
                
                // 标记 DisplayContent 需要 layout
                win.setDisplayLayoutNeeded();
                 // 2. 请求窗口刷新
                mWindowPlacerLocked.requestTraversal();
            }
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}

Android V app 冷启动(5) 窗口动画就绪 中分析过 WMS 处理启动窗口 finish drawing,并且在执行 Transition 动画时, ActivityRecord#mVisible 被更新为 true。那么,WMS 处理真窗的 finish drawing,会导致真窗的绘制状态切换到 HAS_DRAWN,并且在 prepare surface 中,会 show 真窗 surface。

当真窗绘制完成,在窗口刷新的时候,还会触发启动窗口的移除,如下

// WindowState.java

boolean performShowLocked() {
    // ...


    final int drawState = mWinAnimator.mDrawState;
    if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
        if (mAttrs.type != TYPE_APPLICATION_STARTING) {
            // ActivityRecord 处理真窗显示
            mActivityRecord.onFirstWindowDrawn(this);
        } else {
            // ...
        }
    }

    // ActivityRecord#mVisible 此时为 true,可以 show 真窗 surface
    if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
        return false;
    }

    // ...
    
    // Force the show in the next prepareSurfaceLocked() call.
    mWinAnimator.mLastAlpha = -1;
    ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
    // 绘制状态切换到 HAS_DRAWN
    mWinAnimator.mDrawState = HAS_DRAWN;

    // ..

    return true;
}
// ActivityRecord.java

void onFirstWindowDrawn(WindowState win) {
    firstWindowDrawn = true;
    
    // ...

    // Remove starting window directly if is in a pure task. Otherwise if it is associated with
    // a task (e.g. nested task fragment), then remove only if all visible windows in the task
    // are drawn.
    final Task associatedTask = task.mSharedStartingData != null ? task : null;
    if (associatedTask == null) {
        // 真窗绘制完成,就开始移除启动窗口
        removeStartingWindow();
    } else if (associatedTask.getActivity(
            r -> r.isVisibleRequested() && !r.firstWindowDrawn) == null) {
        // ...
    }
    updateReportedVisibilityLocked();
}


void removeStartingWindow() {
    // ...
    
    removeStartingWindowAnimation(true /* prepareAnimation */);

    // ...
}


// prepareAnimation 为 true
void removeStartingWindowAnimation(boolean prepareAnimation) {
    // ...

    final StartingSurfaceController.StartingSurface surface;
    final boolean animate;
    final boolean hasImeSurface;
    if (mStartingData != null) {
        
        // 如果正在执行 Transition 动画,或者 Transition 正在收集当前 ActivityRecord
        // 那么,此时不应该移除启动窗口,而是在 ActivityRecord#onSyncTransactionCommitted() 
        // 中继续移除启动窗口
        if (mStartingData.mWaitForSyncTransactionCommit
                || mTransitionController.isCollecting(this)) {
            mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
            mStartingData.mPrepareRemoveAnimation = prepareAnimation;
            return;
        }
        
        // true
        animate = prepareAnimation && mStartingData.needRevealAnimation()
                && mStartingWindow.isVisibleByPolicy();
        // false
        hasImeSurface = mStartingData.hasImeSurface();
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Schedule remove starting %s startingWindow=%s"
                        + " animate=%b Callers=%s", this, mStartingWindow, animate,
                Debug.getCallers(5));
        surface = mStartingSurface;
        mStartingData = null;
        mStartingSurface = null;
        mStartingWindow = null;
        mTransitionChangeFlags &= ~FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
        if (surface == null) {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
                    + "startingSurface==null, couldn't remove");
            return;
        }
    } else {
        // ...
    }
    
    
    // StartingSurfaceController 移除启动窗口
    surface.remove(animate, hasImeSurface);
}
// StartingSurfaceController.java

public class StartingSurfaceController {
    // ...

    final class StartingSurface {
        private final Task mTask;
        final ITaskOrganizer mTaskOrganizer;

        StartingSurface(Task task, ITaskOrganizer taskOrganizer) {
            mTask = task;
            mTaskOrganizer = taskOrganizer;
        }

        public void remove(boolean animate, boolean hasImeSurface) {
            synchronized (mService.mGlobalLock) {
                // 通知 WMShell 移除启动窗口
                mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
                        mTaskOrganizer, animate, hasImeSurface);
            }
        }
    }
}
// TaskOrganizerController.java

void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation,
        boolean hasImeSurface) {
    final Task rootTask = task.getRootTask();
    if (rootTask == null) {
        return;
    }
    
    // WMShell 会注册一个 ITaskOrganizer
    final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer
            : getTaskOrganizer();
    if (lastOrganizer == null) {
        return;
    }
    
    // StartingWindowRemovalInfo 包装了移除启动窗口的数据
    final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
    removalInfo.taskId = task.mTaskId;
    // true
    // 代表移除启动窗口时,需要以动画的方式执行
    removalInfo.playRevealAnimation = prepareAnimation
            && task.getDisplayContent() != null
            && task.getDisplayInfo().state == Display.STATE_ON;
    // true
    // 表示需要对真窗 leash 做动画
    final boolean playShiftUpAnimation = !task.inMultiWindowMode();
    final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
    if (topActivity != null) {
        // Set defer remove mode for IME
        final DisplayContent dc = topActivity.getDisplayContent();
        if (hasImeSurface) {
            
        }

        // 这里获取到的是真窗 WindowState
        final WindowState mainWindow =
                topActivity.findMainWindow(false/* includeStartingApp */);

        if (mainWindow == null || mainWindow.mRemoved) {
            // ...
        } else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
            removalInfo.roundedCornerRadius =
                    topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
            // 此时并不是真正对真窗 WindowState 做动画,而只是为了获得真窗 leash
            removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
            // 真窗 frame
            removalInfo.mainFrame = new Rect(mainWindow.getFrame());
            removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
                    mainWindow.mSurfacePosition.y);
        }
    }
    try {
        // 通知 WMShell 执行移除启动窗口
        lastOrganizer.removeStartingWindow(removalInfo);
    } catch (RemoteException e) {
        Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
    }
}

WMShell 移除启动窗口

// ShellTaskOrganizer.java

public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
    if (mStartingWindow != null) {
        mStartingWindow.removeStartingWindow(removalInfo);
    }
}
// StartingWindowController.java

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

public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
    if (removalInfo.windowlessSurface) {
        // ...
    } else {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                "Task start finish, remove starting surface for task: %d",
                removalInfo.taskId);
        mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
    }
}
// StartingSurfaceDrawer.java

public class StartingSurfaceDrawer {

    static class StartingWindowRecordManager {

        void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
            final int taskId = removeInfo.taskId;
            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
            if (record != null) {
                final boolean canRemoveRecord = record.removeIfPossible(removeInfo, immediately);
                if (canRemoveRecord) {
                    mStartingWindowRecords.remove(taskId);
                }
            }
        }
        
    }

}
// SplashscreenWindowCreator.java

class SplashscreenWindowCreator extends AbsSplashWindowCreator {

    private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {

        public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
            // ...
            
            // immediately 为 false, mSuggestType 为 STARTING_WINDOW_TYPE_SPLASH_SCREEN
            if (immediately
                    || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
                // ...
            } else {
                if (info.playRevealAnimation) {
                    mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
                            info.windowAnimationLeash, info.mainFrame,
                            // 注意,这个参数是动画执行完成的回调
                            () -> removeWindowInner(mRootView, true),
                            mCreateTime, info.roundedCornerRadius);
                } else {
                    // ...
                }
            }
            return true;
        }
        
    }

}
// SplashscreenContentDrawer.java

/**
 * Create and play the default exit animation for splash screen view.
 */
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
        Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
    final Runnable playAnimation = () -> {
        final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
                view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
                roundedCornerRadius);
        animation.startAnimations();
    };
    
    // ...
    
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
            "applyExitAnimation delayed: %s", delayed);
            
    // 无论走那条路,最终都会执行 playAnimation
    if (delayed > 0) {
        view.postDelayed(playAnimation, delayed);
    } else {
        playAnimation.run();
    }
}
// SplashScreenExitAnimation.java

SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
        Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
        float roundedCornerRadius) {
    mSplashScreenView = view;
    mFirstWindowSurface = leash;
    mRoundedCornerRadius = roundedCornerRadius;
    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) {

    } 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);
            
    // 对于手机/平板来说,mAnimationType 为 0,动画的方式是 radial vanish + slide up
    // 可以把这个配置改为 1,动画的方式是 fade out
    // TODO:对于性能比较差的手机/平板,把配置改为1,是否对性能有帮助?
    mAnimationType = context.getResources().getInteger(
            R.integer.starting_window_exit_animation_type);
            
    mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
    mMainWindowShiftLength = mainWindowShiftLength;
    mFinishCallback = handleFinish;
    mTransactionPool = pool;
}

void startAnimations() {
    SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView,
            mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame,
            mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha,
            mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius);
}
// SplashScreenExitAnimationUtils.java

static void startAnimations(@ExitAnimationType int animationType,
        ViewGroup splashScreenView, SurfaceControl firstWindowSurface,
        int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame,
        int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
        float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
        Animator.AnimatorListener animatorListener, float roundedCornerRadius) {
    ValueAnimator animator;
    if (animationType == TYPE_FADE_OUT) {
        // ...
    } else {
        // 创建一个 ValueAnimator
        animator = createRadialVanishSlideUpAnimator(splashScreenView,
                firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
                animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
                appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
    }
    
    // 执行 ValueAnimator
    animator.start();
}



/**
 * Creates the animator to fade out the icon, reveal the app, and shift up main window.
 */
private static ValueAnimator createRadialVanishSlideUpAnimator(ViewGroup splashScreenView,
        SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
        TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
        int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
        int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
        float roundedCornerRadius) {
    // reveal app
    final float transparentRatio = 0.8f;
    final int globalHeight = splashScreenView.getHeight();
    final int verticalCircleCenter = 0;
    final int finalVerticalLength = globalHeight - verticalCircleCenter;
    final int halfWidth = splashScreenView.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};

    // RadialVanishAnimation 是一个 View
    // 在构造函数中,SplashScreenView 添加了 RadialVanishAnimation
    // SplashScreenView 是一个 FrameLayout
    RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
    radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
    radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
    radialVanishAnimation.setRadialPaintParam(colors, stops);

    View occludeHoleView = null;
    ShiftUpAnimation shiftUpAnimation = null;
    if (firstWindowSurface != null && firstWindowSurface.isValid()) {
    
        if (DEBUG_EXIT_ANIMATION_BLEND) {
            // ...
        } else if (splashScreenView instanceof SplashScreenView) {
            occludeHoleView.setBackgroundColor(
                    ((SplashScreenView) splashScreenView).getInitBackgroundColor());
        } else {
            // ...
        }
        
        // mMainWindowShiftLength 是 20 dp
        final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
        // SplashScreenView 保存 occludeHoleView
        // SplashScreenView 是 FrameLayout
        splashScreenView.addView(occludeHoleView, params);
        
        
        // ShiftUpAnimation 一是操作 occludeHoleView,而是操作真窗 leash
        shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
                firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
                mMainWindowShiftLength, roundedCornerRadius);
    }

    ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
    animator.setDuration(animationDuration);
    animator.setInterpolator(Interpolators.LINEAR);
    
    // animatorListener 由 SplashScreenExitAnimation 实现
    if (animatorListener != null) {
        animator.addListener(animatorListener);
    }
    
    View finalOccludeHoleView = occludeHoleView;
    ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            if (finalShiftUpAnimation != null) {
                finalShiftUpAnimation.finish();
            }
            
            // 动画结束,移除这两个动画的 View
            splashScreenView.removeView(radialVanishAnimation);
            splashScreenView.removeView(finalOccludeHoleView);
        }
    });
    
    
    animator.addUpdateListener(animation -> {
        float linearProgress = (float) animation.getAnimatedValue();

        // Fade out progress
        final float iconProgress =
                ICON_INTERPOLATOR.getInterpolation(getProgress(
                        linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
        View iconView = null;
        View brandingView = null;
        if (splashScreenView instanceof SplashScreenView) {
            iconView = ((SplashScreenView) splashScreenView).getIconView();
            brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
        }
        
        // 1. fade out 动画
        if (iconView != null) {
            iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
        }
        if (brandingView != null) {
            brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
        }

        final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
                appRevealDuration, animationDuration);
        
        // 2. radial vanish 动画
        radialVanishAnimation.onAnimationProgress(revealLinearProgress);

        // 3. shit up 动画
        if (finalShiftUpAnimation != null) {
            finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
        }
    });
    return animator;
}

这里有三个动画

  1. 对启动窗口 View 的 icon view, branding view 做 fade out 动画。
  2. RadialVanishAnimation 是一个 View,在其构造函数中,RadialVanishAnimation 被添加到 SplashScreenView(继承自FrameLayout)中。RadialVanishAnimation 执行动画,就是对 RadialVanishAnimation 这个 View 做 radial vanish 动画。
  3. SplashScreenView(继承自FrameLayout)添加 occludeHoleView。ShiftUpAnimation 不仅对 occludeHoleView 做 shift up 动画,还会对真窗 leash 做 shift up 动画。

个人感觉,除了 fade out 动画,其他都是些花里胡哨的东西。对于性不能不好的机器,完全可以只做 fade out 动画即可。

radial vanish 动画,是一个自定义 View 的动画,有基础的读者可以自行分析下。这里来看下 shift up 动画

// SplashScreenExitAnimationUtils.java

public static final class ShiftUpAnimation {

    public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
                            SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
                            TransactionPool transactionPool, Rect firstWindowFrame,
                            int mainWindowShiftLength, float roundedCornerRadius) {
        // 0
        mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
        // -20
        mToYDelta = toYDelta;
        mOccludeHoleView = occludeHoleView;
        mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
        
        // 真窗 leash
        mFirstWindowSurface = firstWindowSurface;
        
        mSplashScreenView = splashScreenView;
        mTransactionPool = transactionPool;
        mFirstWindowFrame = firstWindowFrame;
        
        // 20
        mMainWindowShiftLength = mainWindowShiftLength;
    }

    void onAnimationProgress(float linearProgress) {
        if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
                || !mSplashScreenView.isAttachedToWindow()) {
            return;
        }

        final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
        final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;

        // mOccludeHoleView 做 transaltate y 动画:0 -> -20
        mOccludeHoleView.setTranslationY(dy);
        
        // mTmpTransform 操作的是真窗 leash 的 y: 20 -> 0
        mTmpTransform.setTranslate(0 /* dx */, dy);
        final SurfaceControl.Transaction tx = mTransactionPool.acquire();
        // transaction 绑定当前 Vsync id,表示需要在当前帧处理这个动画
        tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
        mTmpTransform.postTranslate(mFirstWindowFrame.left,
                mFirstWindowFrame.top + mMainWindowShiftLength);
        SyncRtSurfaceTransactionApplier.SurfaceParams
                params = new SyncRtSurfaceTransactionApplier.SurfaceParams
                .Builder(mFirstWindowSurface)
                .withMatrix(mTmpTransform)
                .withMergeTransaction(tx)
                .build();
        mApplier.scheduleApply(params);

        mTransactionPool.release(tx);
    }
}

对真窗 leash 的操作,是通过监听 occludeHoleView 的硬件加速渲染,以达到 leash 动画和 occludeHoleView 同时执行,如下

// SyncRtSurfaceTransactionApplier.java

public void scheduleApply(final SurfaceParams... params) {
    if (mTargetViewRootImpl == null) {
        return;
    }
    
    // mTargetViewRootImpl 是 occludeHoleView
    mTargetSc = mTargetViewRootImpl.getSurfaceControl();
    
    // 创建一个 transaction
    final Transaction t = new Transaction();
    
    // 通过 transaction 保存要对真窗 leash 执行的操作
    applyParams(t, params);

    // 监听 occludeHoleView 的硬件渲染
    mTargetViewRootImpl.registerRtFrameCallback(frame -> {
        if (mTargetSc != null && mTargetSc.isValid()) {
            // 执行 transaction 
            applyTransaction(t, frame);
        }
        // The transaction was either dropped, successfully applied, or merged with a future
        // transaction, so we can safely release its resources.
        t.close();
    });

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


void applyTransaction(Transaction t, long frame) {
    if (mTargetViewRootImpl != null) {
        // t 是对 leash 操作的 transaction,把它与 occludeHoleView 的 transaction 一起执行
        mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
    } else {
        t.apply();
    }
}

当所有动画执行完成,执行回调

// SplashScreenExitAnimation.java

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

private void reset() {
    if (DEBUG_EXIT_ANIMATION) {
        Slog.v(TAG, "vanish animation finished");
    }

    if (mSplashScreenView.isAttachedToWindow()) {
        // 把 SplashScreenView 隐藏掉
        // 注意,SplashScreenView 并不是启动窗口的 root view,而只是其主要的一部分
        mSplashScreenView.setVisibility(GONE);
        
        // mFinishCallback 是 SplashscreenWindowCreator#removeWindowInner()
        if (mFinishCallback != null) {
            mFinishCallback.run();
            mFinishCallback = null;
        }
    }
}
// SplashscreenWindowCreator.java

// decorView 不是 SlashScreenView,而是它的 parent view,也是启动窗口的 root view
// hideView 为 true
private void removeWindowInner(@NonNull View decorView, boolean hideView) {
    requestTopUi(false);
    if (!decorView.isAttachedToWindow()) {
        return;
    }
    
    // 启动窗口 root view 可见性更新为 GONE
    // 会导致 WMS 对启动窗口 surface 做 fade out 动画
    if (hideView) {
        decorView.setVisibility(View.GONE);
    }
    
    // 通过 WindowManagerGlobal 移除启动窗口 root view,
    // 会导致 WMS 移除整个启动窗口,包括启动窗口 surface
    mWindowManagerGlobal.removeView(decorView, false /* immediate */);
}