Android U 分屏——退出流程

发布于:2025-06-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

根据shell动画开关persist.wm.debug.shell_transit配置项值(默认值为1)的不同,所触发的流程不同。
通过命令adb shell setprop persist.wm.debug.shell_transit 1可以用来设置配置项的值。
分屏退出的操作在SystemUI侧先处理,然后再到system_server侧对task进行处理。

通过返回键退出分屏

按下back键退出分屏

shell动画开启

persist.wm.debug.shell_transit值为1(默认)

SplitScreen: prepareExitSplitScreen stageToTop:1
SplitScreen: java.lang.Exception
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.prepareExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1518)
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.handleRequest(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2410)
SplitScreen: 	at com.android.wm.shell.transition.Transitions.requestStartTransition(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1075)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.lambda$requestStartTransition$1(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1360)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.$r8$lambda$FkH1M0vUh3zDx8R5iMruInPEXLI(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl$$ExternalSyntheticLambda1.run(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at android.os.Handler.handleCallback(Handler.java:958)
SplitScreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
SplitScreen: 	at android.os.Looper.loopOnce(Looper.java:205)
SplitScreen: 	at android.os.Looper.loop(Looper.java:294)
SplitScreen: 	at android.os.HandlerThread.run(HandlerThread.java:67)

shell动画关闭

persist.wm.debug.shell_transit值为0

Splitscreen: applyExitSplitScreen
Splitscreen: java.lang.Exception
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.applyExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1361)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.exitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1356)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.onSnappedToDismiss(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2108)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout.lambda$flingDividerToDismiss$3(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:594)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout.$r8$lambda$700WKN2Dd7W4zPNbeSIsGlBkTTo(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout$$ExternalSyntheticLambda5.run(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout$1.onAnimationEnd(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:633)
Splitscreen: 	at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:711)
Splitscreen: 	at android.animation.Animator$AnimatorCaller$$ExternalSyntheticLambda1.call(Unknown Source:4)
Splitscreen: 	at android.animation.Animator.callOnList(Animator.java:669)
Splitscreen: 	at android.animation.Animator.notifyListeners(Animator.java:608)
Splitscreen: 	at android.animation.Animator.notifyEndListeners(Animator.java:633)
Splitscreen: 	at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1306)
Splitscreen: 	at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1566)
Splitscreen: 	at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:328)
Splitscreen: 	at android.animation.AnimationHandler.-$$Nest$mdoAnimationFrame(Unknown Source:0)
Splitscreen: 	at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:86)
Splitscreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1337)
Splitscreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
Splitscreen: 	at android.view.Choreographer.doCallbacks(Choreographer.java:952)
Splitscreen: 	at android.view.Choreographer.doFrame(Choreographer.java:878)
Splitscreen: 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
Splitscreen: 	at android.os.Handler.handleCallback(Handler.java:958)
Splitscreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
Splitscreen: 	at android.os.Looper.loopOnce(Looper.java:205)
Splitscreen: 	at android.os.Looper.loop(Looper.java:294)

使用分割线拖拽退出分屏

分屏状态下分割线拖动至某个阈值时会退出分屏

shell动画开启

persist.wm.debug.shell_transit值为1(默认)

SplitScreen: prepareExitSplitScreen stageToTop:1
SplitScreen: java.lang.Exception
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.prepareExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1518)
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.onSnappedToDismiss(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2119)
SplitScreen: 	at com.android.wm.shell.common.split.SplitLayout.lambda$snapToTarget$1(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:535)
SplitScreen: 	at com.android.wm.shell.common.split.SplitLayout.$r8$lambda$mzgqhbOmxb8OeJ0DEzq0VaaDWTg(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at com.android.wm.shell.common.split.SplitLayout$$ExternalSyntheticLambda2.run(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at com.android.wm.shell.common.split.SplitLayout$1.onAnimationEnd(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:633)
SplitScreen: 	at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:711)
SplitScreen: 	at android.animation.Animator$AnimatorCaller$$ExternalSyntheticLambda1.call(Unknown Source:4)
SplitScreen: 	at android.animation.Animator.callOnList(Animator.java:669)
SplitScreen: 	at android.animation.Animator.notifyListeners(Animator.java:608)
SplitScreen: 	at android.animation.Animator.notifyEndListeners(Animator.java:633)
SplitScreen: 	at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1306)
SplitScreen: 	at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1566)
SplitScreen: 	at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:328)
SplitScreen: 	at android.animation.AnimationHandler.-$$Nest$mdoAnimationFrame(Unknown Source:0)
SplitScreen: 	at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:86)
SplitScreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1337)
SplitScreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
SplitScreen: 	at android.view.Choreographer.doCallbacks(Choreographer.java:952)
SplitScreen: 	at android.view.Choreographer.doFrame(Choreographer.java:878)
SplitScreen: 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
SplitScreen: 	at android.os.Handler.handleCallback(Handler.java:958)
SplitScreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
SplitScreen: 	at android.os.Looper.loopOnce(Looper.java:205)
SplitScreen: 	at android.os.Looper.loop(Looper.java:294)
SplitScreen: 	at android.os.HandlerThread.run(HandlerThread.java:67)

shell动画关闭

persist.wm.debug.shell_transit值为0

Splitscreen: applyExitSplitScreen
Splitscreen: java.lang.Exception
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.applyExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1361)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.exitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1356)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.onSnappedToDismiss(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2108)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout.lambda$snapToTarget$0(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:530)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout.$r8$lambda$oU9r3E0xmWtjVrFVXY5B4lkKX7c(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout$$ExternalSyntheticLambda1.run(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
Splitscreen: 	at com.android.wm.shell.common.split.SplitLayout$1.onAnimationEnd(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:633)
Splitscreen: 	at android.animation.Animator$AnimatorListener.onAnimationEnd(Animator.java:711)
Splitscreen: 	at android.animation.Animator$AnimatorCaller$$ExternalSyntheticLambda1.call(Unknown Source:4)
Splitscreen: 	at android.animation.Animator.callOnList(Animator.java:669)
Splitscreen: 	at android.animation.Animator.notifyListeners(Animator.java:608)
Splitscreen: 	at android.animation.Animator.notifyEndListeners(Animator.java:633)
Splitscreen: 	at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1306)
Splitscreen: 	at android.animation.ValueAnimator.doAnimationFrame(ValueAnimator.java:1566)
Splitscreen: 	at android.animation.AnimationHandler.doAnimationFrame(AnimationHandler.java:328)
Splitscreen: 	at android.animation.AnimationHandler.-$$Nest$mdoAnimationFrame(Unknown Source:0)
Splitscreen: 	at android.animation.AnimationHandler$1.doFrame(AnimationHandler.java:86)
Splitscreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1337)
Splitscreen: 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
Splitscreen: 	at android.view.Choreographer.doCallbacks(Choreographer.java:952)
Splitscreen: 	at android.view.Choreographer.doFrame(Choreographer.java:878)
Splitscreen: 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
Splitscreen: 	at android.os.Handler.handleCallback(Handler.java:958)
Splitscreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
Splitscreen: 	at android.os.Looper.loopOnce(Looper.java:205)
Splitscreen: 	at android.os.Looper.loop(Looper.java:294)
Splitscreen: 	at android.os.HandlerThread.run(HandlerThread.java:67)

启动某个分屏中的应用退出分屏

这种场景一般是在分屏场景下,按HOME键回到桌面,然后再点启动之前分屏中的任意一个应用时,此时的应用会全屏显示而非分屏,会触发该退出流程。或者直接在分屏页面下拉控制中心后,启动设置等操作。

shell动画开启

persist.wm.debug.shell_transit值为1(默认)

SplitScreen: prepareExitSplitScreen stageToTop:-1
SplitScreen: java.lang.Exception
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.prepareExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1518)
SplitScreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.handleRequest(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2442)
SplitScreen: 	at com.android.wm.shell.transition.Transitions.dispatchRequest(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:931)
SplitScreen: 	at com.android.wm.shell.transition.DefaultMixedHandler.handleRequest(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:195)
SplitScreen: 	at com.android.wm.shell.transition.Transitions.requestStartTransition(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1075)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.lambda$requestStartTransition$1(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1360)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.$r8$lambda$FkH1M0vUh3zDx8R5iMruInPEXLI(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl$$ExternalSyntheticLambda1.run(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
SplitScreen: 	at android.os.Handler.handleCallback(Handler.java:958)
SplitScreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
SplitScreen: 	at android.os.Looper.loopOnce(Looper.java:205)
SplitScreen: 	at android.os.Looper.loop(Looper.java:294)
SplitScreen: 	at android.os.HandlerThread.run(HandlerThread.java:67)

shell动画关闭

persist.wm.debug.shell_transit值为0

Splitscreen: java.lang.Exception
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.applyExitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1361)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.exitSplitScreen(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:1356)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.onStageHasChildrenChanged(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:2067)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator.-$$Nest$monStageHasChildrenChanged(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:0)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageCoordinator$StageListenerImpl.onStatusChanged(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:3241)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageTaskListener.sendStatusChanged(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:447)
Splitscreen: 	at com.android.wm.shell.splitscreen.StageTaskListener.onTaskVanished(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:280)
Splitscreen: 	at com.android.wm.shell.ShellTaskOrganizer.updateTaskListenerIfNeeded(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:654)
Splitscreen: 	at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(go/retraceme 6f05956d209492c74243b49dd6141e0b4e6a7013b057ea6bd2e2643037fbaaa6:526)
Splitscreen: 	at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6(TaskOrganizer.java:340)
Splitscreen: 	at android.window.TaskOrganizer$1.$r8$lambda$FmJPvZyGqAGeVe9o6dSQkNL3f3g(Unknown Source:0)
Splitscreen: 	at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
Splitscreen: 	at android.os.Handler.handleCallback(Handler.java:958)
Splitscreen: 	at android.os.Handler.dispatchMessage(Handler.java:99)
Splitscreen: 	at android.os.Looper.loopOnce(Looper.java:205)
Splitscreen: 	at android.os.Looper.loop(Looper.java:294)
Splitscreen: 	at android.os.HandlerThread.run(HandlerThread.java:67)

代码分析

从上面堆栈中可以看出,这个方法中会根据配置项persist.wm.debug.shell_transit的值进行不同的退出方法处理。
最终退出分屏的方法分为StageCoordinator.prepareExitSplitScreenStageCoordinator.applyExitSplitScreen根据配置项的值判断。
persist.wm.debug.shell_transit值为1,使用prepareExitSplitScreen退出;
persist.wm.debug.shell_transit值为0,使用applyExitSplitScreen退出。

这里以onSnappedToDismiss为起点分析,常用于分割线退出分屏。
以SplitLayout.snapToTarget中回调的onSnappedToDismiss方法,即分割线滑动到顶部退出分屏,为例:

    public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
        switch (snapTarget.flag) {
            //FLAG_DISMISS_START:代表分割线上滑到顶部,导致上分屏退出情况。
            //即在5个分割区域的最顶部松手
            case FLAG_DISMISS_START:
                flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                        () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                EXIT_REASON_DRAG_DIVIDER));
                break;
            ......

bottomOrRight:值为false,表示是上分屏(默认情况是SideStage)退出。
EXIT_REASON_DRAG_DIVIDER:一个字符串,表示退出分屏的理由。

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

    @Override
    public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
        //bottomOrRight传递值false,判断SideStage是否在顶部(竖屏)或者左边(横屏);反之同理。
        //mSideStagePosition默认为0,SideStage在顶端;值为1时,在底端。
        //主要目的就是判断是退出的是哪个stage
        final boolean mainStageToTop =
                bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
                        : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
        //根据mainStageToTop,获取没有退出stage
        final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
        //判断配置项persist.wm.debug.shell_transit的值
        if (!ENABLE_SHELL_TRANSITIONS) {
            //传递需要退出的stage,通过exitSplitScreen退出分屏
            exitSplitScreen(toTopStage, reason);
            return;
        }

        //mainStageToTop传递值false,dismissTop值为STAGE_TYPE_SIDE。
        final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        //重置bounds
        toTopStage.resetBounds(wct);
        //传递stage类型,退出分屏
        prepareExitSplitScreen(dismissTop, wct);
        if (mRootTaskInfo != null) {
            wct.setDoNotPip(mRootTaskInfo.token);
        }
        //shell动画处理,并且提交WindowContainerTransaction
        mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
    }

这里我们继续以bottomOrRight值为false代入该方法中,可以得出mainStageToTop值为1。(不考虑上下分屏互换场景,取mSideStagePosition默认0,SPLIT_POSITION_TOP_OR_LEFT是固定值也为0)
因此toTopStage最终获取就是MainStage

这里代码中很清晰的通过配置项persist.wm.debug.shell_transit的不同进行了不同的退出分屏调用,即exitSplitScreen和prepareExitSplitScreen。
需要注意的是prepareExitSplitScreen涉及相应的WindowContainerTransaction操作,其提交是在mSplitTransitions.startDismissTransition处理的。
下面我们分别来看看这两个方法。

exitSplitScreen

exitSplitScreen(toTopStage, reason);
这里接上述,toTopStage值为MainStage

    private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
            @ExitReason int exitReason) {
        if (!mMainStage.isActive()) return;

        final WindowContainerTransaction wct = new WindowContainerTransaction();
        //前面toTopStage的值传递给了childrenToTop
        applyExitSplitScreen(childrenToTop, wct, exitReason);
    }

    private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
            WindowContainerTransaction wct, @ExitReason int exitReason) {
        if (!mMainStage.isActive() || mIsExiting) return;

        ......
        //判断childrenToTop(当前获取的stage是否为null),或该stage下的task的ID是否有效
        if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
            ......
        } else {
            // Expand to top side split as full screen for fading out decor animation and dismiss
            // another side split(Moving its children to bottom).
            mIsExiting = true;
            //重置该stage的bounds
            childrenToTop.resetBounds(wct);
            //传递stage的task的token,通过WindowContainerTransaction的reorder方法调整stage位置
            wct.reorder(childrenToTop.mRootTaskInfo.token, true);
        }
        //这里传递的是分屏的中task的token,主要就是把分屏task下的叶子节点重新重新分配挂载
        wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                false /* reparentLeafTaskIfRelaunch */);
        mSyncQueue.queue(wct);
        mSyncQueue.runInSync(t -> {
            t.setWindowCrop(mMainStage.mRootLeash, null)
                    .setWindowCrop(mSideStage.mRootLeash, null);
            t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
            setDividerVisibility(false, t);

            //判断当前stage是否为null
            if (childrenToTop == null) {
                t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
            } else {
                // In this case, exit still under progress, fade out the split decor after first WCT
                // done and do remaining WCT after animation finished.
                childrenToTop.fadeOutDecor(() -> {
                    WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
                    mIsExiting = false;
                    //重新挂载MainStage下的Task,如果childrenToTop是MainStage,则在前台显示之前挂载在MainStage下的task
                    mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
                    //重新挂载SideStage下的Task,如果childrenToTop是SideStage,则在前台显示之前挂载在SideStage下的task
                    mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                    //重新排列层级结构
                    finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
                    setRootForceTranslucent(true, finishedWCT);
                    finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
                    mSyncQueue.queue(finishedWCT);
                    mSyncQueue.runInSync(at -> {
                        at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
                    });
                    onTransitionAnimationComplete();
                });
            }
        });

        Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
        // Log the exit
        if (childrenToTop != null) {
            logExitToStage(exitReason, childrenToTop == mMainStage);
        } else {
            logExit(exitReason);
        }
    }

这个里面主要的操作其实就是重新挂载各stage下的task。

mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);

我们这里childrenToTop传递的就是MainStage,因此MainStage下的task会前台显示,这也对应了我们向上滑动,以退出SideStage的方式退出分屏后,显示仍然是MainStage下的task。

prepareExitSplitScreen

prepareExitSplitScreen(dismissTop, wct);

    /**
     * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
     * an existing WindowContainerTransaction (rather than applying immediately). This is intended
     * to be used when exiting split might be bundled with other window operations.
     */
    void prepareExitSplitScreen(@StageType int stageToTop,
            @NonNull WindowContainerTransaction wct) {
            
        if (!mMainStage.isActive()) return;
        mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
        mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
    }

这个方法就更为简单,直接调用两个Stage的移除。

分屏退出Task

退出分屏的方法分为StageCoordinator.prepareExitSplitScreenStageCoordinator.applyExitSplitScreen,使用哪个方法退出,取决于persist.wm.debug.shell_transit的值。
persist.wm.debug.shell_transit值为1,使用prepareExitSplitScreen退出;
persist.wm.debug.shell_transit值为0,使用applyExitSplitScreen退出。
无论是哪种退出方式,最终都会通过SideStage.removeAllTasksMainStage.deactivate分别reparentTask,最终会在system_server 侧处理。

这里分屏的退出并不会移除创建好的分屏task,reparentTask操作只是把上下分屏Task下挂载的应用Task移动到DefaultTaskDisplayArea下挂载。
退出分屏前:
在这里插入图片描述

退出分屏后:
在这里插入图片描述
这里我们可以看到应用Task=64和Task=29不在挂载到原来的分屏Task上。
注:按返回键退出分屏,退出来Task=29的应用,因此这里没有显示该应用task。

SideStage reparentTasks

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java

    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
        if (mChildrenTaskInfo.size() == 0) return false;
        wct.reparentTasks(
                mRootTaskInfo.token,
                null /* newParent */,
                null /* windowingModes */,
                null /* activityTypes */,
                toTop);
        return true;
    }

这里mRootTaskInfo.token指的就是SideStage。

MainStage reparentTasks

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java

    void deactivate(WindowContainerTransaction wct, boolean toTop) {
        if (!mIsActive) return;
        mIsActive = false;

        if (mRootTaskInfo == null) return;
        final WindowContainerToken rootToken = mRootTaskInfo.token;
        wct.reparentTasks(
                rootToken,
                null /* newParent */,
                null /* windowingModes */,
                null /* activityTypes */,
                toTop);
    }

这里rootToken指的就是MainStage。

这两个方法后续会走到system_server侧,通过WindowContainerTransaction事务处理,见Android U system_server侧WindowContainerTransaction处理reparentTasks相关流程。


网站公告

今日签到

点亮在社区的每一天
去签到