Android U 多任务启动分屏——Launcher流程(下分屏)

前文

Android U 多任务启动分屏——Launcher流程(上分屏)
前文讲了Launcher中上分屏流程,现在我们看看下分屏的流程。

最近任务onClick事件的监听

在最近任务中每个任务都是一个TaskView,对TaskView操作,就是每个任务的操作。
在这里插入图片描述

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java

    public TaskView(
            Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        //从给定的context获取当前的StatefulActivity实例
        //绑定TaskView和Activity之间的关系
        mActivity = StatefulActivity.fromContext(context);
        //监听当前TaskView的点击事件
        setOnClickListener(this::onClick);

        //初始化绘制相关参数
        mCurrentFullscreenParams = new FullscreenDrawParams(context);
        //把当前的Activity以及其对应的TaskView传递给DigitalWellBeingToast
        mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);

        //判断是否需要启用键盘焦点高亮
        boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
                || DesktopTaskView.DESKTOP_MODE_SUPPORTED;

        //如果不需要启用键盘焦点高亮,则setWillNotDraw(true),表示这个视图不需要绘制
        setWillNotDraw(!keyboardFocusHighlightEnabled);
        //使用TypedArray从XML属性中读取TaskView的自定义属性
        TypedArray ta = context.obtainStyledAttributes(
                attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);

        //如果启用了键盘焦点高亮,则创建一个BorderAnimator实例。
        mBorderAnimator = !keyboardFocusHighlightEnabled
                ? null
                : new BorderAnimator(
                        /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
                        /* borderColor= */ ta.getColor(
                                R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
                        /* borderAnimationParams= */ new BorderAnimator.SimpleParams(
                                /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
                                        R.dimen.keyboard_quick_switch_border_width),
                                /* boundsBuilder= */ this::updateBorderBounds,
                                /* targetView= */ this));
        //回收TypedArray对象以避免内存泄漏。
        ta.recycle();
    }

    private void onClick(View view) {
        if (getTask() == null) {
            return;
        }
        //是否选择第二个分屏的应用
        if (confirmSecondSplitSelectApp()) {
            return;
        }
        //运行当前选择的Task
        launchTasks();
        mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                .log(LAUNCHER_TASK_LAUNCH_TAP);
    }

onClick方法中,如果我们选择了第二个分屏的应用那么就进入到分屏流程confirmSecondSplitSelectApp(),这个方法中同样会进行一些判断当前是否进入分屏,还是直接运行当前TasklaunchTasks()

选择下分屏

确定选择下分屏,播放相关动画,调用SytemUI接口进入到真正分屏

确定下分屏task

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java


    /**
     * @return {@code true} if user is already in split select mode and this tap was to choose the
     *         second app. {@code false} otherwise
     */
    protected boolean confirmSecondSplitSelectApp() {
        //获取最后一次点击的Task的索引
        int index = getLastSelectedChildTaskIndex();
        //获取该索引对应的Task相关参数
        TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
        //判断TaskIdAttributeContainer对象是否为空
        if (container != null) {
            //进入confirmSplitSelect方法判断分屏的后续流程
            return getRecentsView().confirmSplitSelect(this, container.getTask(),
                    container.getIconView().getDrawable(), container.getThumbnailView(),
                    container.getThumbnailView().getThumbnail(), /* intent */ null,
                    /* user */ null);
        }
        return false;
    }
    
    public RecentsView getRecentsView() {
        return (RecentsView) getParent();
    }

前文我们分析过,这里的getRecentsView()返回的是当前TaskView的父View,类型强制转换为了RecentsView,而RecentsView抽象类,优先调用的应该是其子类的confirmSplitSelect方法。
getRecentsView()对应的就是com.android.quickstep.views.LauncherRecentsView{b2fe033 V.ED..... ........ 0,0-1440,2960 #7f09022a app:id/overview_panel},即TaskViewViewLauncherRecentsView
LauncherRecentsViewRecentsView的子类之一,但是LauncherRecentsView中没有confirmSplitSelect方法,所以这里调用的是其父类RecentsView中的confirmSplitSelect方法(儿子没有的找父亲要,总之就是找老登爆点金币)。
confirmSplitSelect方法传递的参数this指的就是选择的下分屏task。

动画准备和启动

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java

    /**
     * Confirms the selection of the next split task. The extra data is passed through because the
     * user may be selecting a subtask in a group.
     *
     * @param containerTaskView If our second selected app is currently running in Recents, this is
     *                          the "container" TaskView from Recents. If we are starting a fresh
     *                          instance of the app from an Intent, this will be null.
     * @param task The Task corresponding to our second selected app. If we are starting a fresh
     *             instance of the app from an Intent, this will be null.
     * @param drawable The Drawable corresponding to our second selected app's icon.
     * @param secondView The View representing the current space on the screen where the second app
     *                   is (either the ThumbnailView or the tapped icon).
     * @param intent If we are launching a fresh instance of the app, this is the Intent for it. If
     *               the second app is already running in Recents, this will be null.
     * @param user If we are launching a fresh instance of the app, this is the UserHandle for it.
     *             If the second app is already running in Recents, this will be null.
     * @return true if waiting for confirmation of second app or if split animations are running,
     *          false otherwise
     */
    public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
            View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user) {
        //检查是否可以全屏启动任务:如果可以,直接返回false,表示不需要分屏
        if (canLaunchFullscreenTask()) {
            return false;
        }
        //检查是否已确认两个分屏应用:就是确认两个taskId是否不为空。
        if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
            return true;
        }
        // Second task is selected either as an already-running Task or an Intent
        //如果task不为null,检查任务是否支持分屏(isDockable)。
        //如果task为null,则使用intent和user设置下分屏任务
        if (task != null) {
            //如果不支持,显示不支持分屏的提示,并返回true
            if (!task.isDockable) {
                // Task does not support split screen
                mSplitUnsupportedToast.show();
                return true;
            }
            //保存下分屏任务的task
            mSplitSelectStateController.setSecondTask(task);
        } else {
            //保存下分屏任务的intent
            mSplitSelectStateController.setSecondTask(intent, user);
        }

        //使用RectF和Rect对象存储第一个和第二个任务的起始和结束边界。
        RectF secondTaskStartingBounds = new RectF();//初始化底部的区域
        Rect secondTaskEndingBounds = new Rect();
        // TODO(194414938) starting bounds seem slightly off, investigate
        Rect firstTaskStartingBounds = new Rect();//初始化顶部的区域
        Rect firstTaskEndingBounds = mTempRect;

        //根据设备是否为平板计算动画时长(timings)
        boolean isTablet = mActivity.getDeviceProfile().isTablet;
        SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
        //创建分屏动画
        PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());

        int halfDividerSize = getResources()
                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
         //获取对应的一个上屏幕区域和下屏幕区域截至区域firstTaskEndingBounds,secondTaskEndingBounds
        mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
                mActivity.getDeviceProfile(),
                mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
                secondTaskEndingBounds);

        //设置mFirstFloatingTaskView和mSecondFloatingTaskView布局的属性和动画
        //获取当前的上屏幕区域坐标情况放入firstTaskStartingBounds
        mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
        //有了初始区域和结束区域,进行相关的动画配置
        mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
                new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                false /* fadeWithThumbnail */, true /* isStagedTask */);

        safeRemoveDragLayerView(mSecondFloatingTaskView);
    
        //与mFirstFloatingTaskView同理
        mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, secondView,
                thumbnail, drawable, secondTaskStartingBounds);
        mSecondFloatingTaskView.setAlpha(1);
        mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
        
        //设置动画透明度
        pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
                timings.getInstructionsFadeStartOffset(),
                timings.getInstructionsFadeEndOffset()));
        
        //设置动画监听,动画结束时,调用launchSplitTasks启动分屏
        //其传递的callback用于重置分屏选择状态。
        pendingAnimation.addEndListener(aBoolean -> {
            mSplitSelectStateController.launchSplitTasks(
                    aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
        });

        mSecondSplitHiddenView = containerTaskView;
        //如果containerTaskView不为null,隐藏下屏的TaskView
        if (mSecondSplitHiddenView != null) {
            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE,
                    mSplitSelectStateController.getSecondTaskId());
        }

        InteractionJankMonitorWrapper.begin(this,
                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");

        // Fade out all other views underneath placeholders
        //使用ObjectAnimator淡出所有其他视图。
        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
        pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
        //启动动画
        pendingAnimation.buildAnim().start();
        return true;
    }

参数:
containerTaskView:如果第二个选中的应用在最近任务中运行,这是从最近任务中获取的“容器”TaskView。如果是通过Intent启动的新实例,则为null。
task:对应于第二个选中应用的Task。如果是通过Intent启动的新实例,则为null。
drawable:第二个选中应用的图标对应的Drawable。
secondView:表示屏幕上第二个应用当前位置的View(可能是缩略图视图或点击的图标)。
thumbnail:第二个应用的缩略图,可以为null。
intent:如果启动的是第二个应用的新实例,这是对应的Intent。如果应用已在最近任务中运行,则为null。
user:如果启动的是第二个应用的新实例,这是对应的UserHandle。如果应用已在最近任务中运行,则为null。

方法作用:
这个方法处理分屏功能中第二个应用的确认选择,包括检查是否支持分屏、设置第二个任务、计算动画边界、创建并启动分屏动画、处理隐藏视图以及性能监控。它返回一个布尔值,指示是否正在等待确认或动画是否正在运行。

关键方法:

  • 下分屏任务的保存

            // Second task is selected either as an already-running Task or an Intent
            //如果task为null,则使用intent和user设置下分屏任务
            if (task != null) {
                ......
                //保存下分屏任务的task
                mSplitSelectStateController.setSecondTask(task);
            } else {
                //保存下分屏任务的intent
                mSplitSelectStateController.setSecondTask(intent, user);
            }
    

    这里不管是用task还是intent的方式,都会调用到SplitSelectStateControlleretSecondTask方法。
    代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java

        /**
         * To be called as soon as user selects the second task (even if animations aren't complete)
         * @param task The second task that will be launched.
         */
        public void setSecondTask(Task task) {
            mSecondTaskId = task.key.id;
    
            if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
                mSplitSelectDataHolder.setSecondTask(task.key.id);
            }
        }
    
        /**
         * To be called as soon as user selects the second app (even if animations aren't complete)
         * @param intent The second intent that will be launched.
         * @param user The user of that intent.
         */
        public void setSecondTask(Intent intent, UserHandle user) {
            mSecondTaskIntent = intent;
            mSecondUser = user;
    
            if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
                mSplitSelectDataHolder.setSecondTask(intent, user);
            }
        }
    

    ENABLE_SPLIT_LAUNCH_DATA_REFACTOR的默认值为true,因此会调用到SplitSelectDataHolder的setSecondTask方法。
    代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt

        /**
         * To be called as soon as user selects the second task (even if animations aren't complete)
         * @param taskId The second task that will be launched.
         */
        fun setSecondTask(taskId: Int) {
            secondTaskId = taskId
        }
    
        /**
         * To be called as soon as user selects the second app (even if animations aren't complete)
         * @param intent The second intent that will be launched.
         * @param user The user of that intent.
         */
        fun setSecondTask(intent: Intent, user: UserHandle) {
            secondIntent = intent
            secondUser = user
        }
    

    从代码上看,这两个方法就是保存taskintent
    目的就是把这些初始的参数保存在SplitSelectDataHolder中,对ENABLE_SPLIT_LAUNCH_DATA_REFACTOR默认值为true的情况时,为后续的分屏做准备(上分屏流程中RecentsViewinitiateSplitSelect里面,调用流程中同样有此操作)。

  • 监听动画结束流程启动分屏

    pendingAnimation.addEndListener(aBoolean -> {
        mSplitSelectStateController.launchSplitTasks(
                aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
        InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
    });
    

    动画结束后,调用的这个方法也是我们真正启动分屏的方法。

启动分屏

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java

    /**
     * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
     * to be launched. Call after launcher side animations are complete.
     */
    public void launchSplitTasks(Consumer<Boolean> callback) {
        Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
                LogUtils.getShellShareableInstanceId();
        launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
                mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
                instanceIds.first);

        mStatsLogManager.logger()
                .withItemInfo(mItemInfo)
                .withInstanceId(instanceIds.second)
                .log(mSplitEvent);
    }

这里我们主要关注其中的launchTasks方法

launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
        mInitialStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
        instanceIds.first);

这些参数传递的就是需要分屏的两个应用的TaskIdIntent,分屏的启动方式一般就是通过TaskIdIntent来确定分屏应用
mInitialTaskId, mInitialTaskIntent:第一个应用的TaskIdIntent
mSecondTaskId, mSecondTaskIntent:第二个应用的TaskIdIntent
mInitialStagePosition:分屏的初始位置(从前面上分屏流程中TaskView.initiateSplitSelect传递过来的值,其值为0)
freezeTaskList:判断是否冻结最近任务列表,这里直接给的值是false
DEFAULT_SPLIT_RATIO:默认的分屏比例
注:其中Intent参数可以为null

    /**
     * To be called when we want to launch split pairs from Overview. Split can be initiated from
     * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
     * fill in intent with a taskId2 are set.
     * @param intent1 is null when split is initiated from Overview
     * @param stagePosition representing location of task1
     * @param shellInstanceId loggingId to be used by shell, will be non-null for actions that
     *                   create a split instance, null for cases that bring existing instaces to the
     *                   foreground (quickswitch, launching previous pairs from overview)
     */
    public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2,
            @Nullable Intent intent2, @StagePosition int stagePosition,
            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
            @Nullable InstanceId shellInstanceId) {

        TestLogging.recordEvent(
                TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
        //ENABLE_SPLIT_LAUNCH_DATA_REFACTOR的值为true
        //通过launchTasksRefactored方法进入分屏
        if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
            launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
            return;
        }

        final ActivityOptions options1 = ActivityOptions.makeBasic();
        if (freezeTaskList) {
            options1.setFreezeRecentTasksReordering();
        }
        boolean hasSecondaryPendingIntent = mSecondPendingIntent != null;

        //根据ENABLE_SHELL_TRANSITIONS(shell动画配置开关)的值决定进入哪个流程
        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
            final RemoteTransition remoteTransition = getShellRemoteTransition(taskId1, taskId2,
                    callback);
            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
                        shellInstanceId);
            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
                        splitRatio, remoteTransition, shellInstanceId);
            } else if (intent1 == null) {
                launchIntentOrShortcut(intent2, mSecondUser, options1, taskId1,
                        getOppositeStagePosition(stagePosition), splitRatio, remoteTransition,
                        shellInstanceId);
            } else {
                mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
                        mInitialUser.getIdentifier(), getShortcutInfo(intent1, mInitialUser),
                        options1.toBundle(), hasSecondaryPendingIntent
                                ? mSecondPendingIntent
                                : getPendingIntent(intent2, mSecondUser),
                        mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
                        null /* options2 */, stagePosition, splitRatio, remoteTransition,
                        shellInstanceId);
            }
        } else {
            final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(taskId1, taskId2,
                    callback);

            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
                        taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
                        shellInstanceId);
            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
                        stagePosition, splitRatio, adapter, shellInstanceId);
            } else if (intent1 == null) {
                launchIntentOrShortcutLegacy(intent2, mSecondUser, options1, taskId1,
                        getOppositeStagePosition(stagePosition), splitRatio, adapter,
                        shellInstanceId);
            } else {
                mSystemUiProxy.startIntentsWithLegacyTransition(
                        getPendingIntent(intent1, mInitialUser), mInitialUser.getIdentifier(),
                        getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
                        hasSecondaryPendingIntent
                                ? mSecondPendingIntent
                                : getPendingIntent(intent2, mSecondUser),
                        mSecondUser.getIdentifier(), getShortcutInfo(intent2, mSecondUser),
                        null /* options2 */, stagePosition, splitRatio, adapter, shellInstanceId);
            }
        }
    }

ENABLE_SPLIT_LAUNCH_DATA_REFACTOR的默认值为true,进入launchTasksRefactored方法。

if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
    launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
    return;
}

其后面的代码本质上都是在符合条件的情况下通过调用SystemUiProxy的分屏方法,在launchTasksRefactored方法中调用方法类似,不再赘述。从堆栈上看一般情况走的也是launchTasksRefactored的流程,这里跟踪launchTasksRefactored方法即可。
注:在android 13中没有该方法,感兴趣的朋友可以跳过该方法看后面的流程即可。

    private void launchTasksRefactored(Consumer<Boolean> callback, boolean freezeTaskList,
            float splitRatio, @Nullable InstanceId shellInstanceId) {
        final ActivityOptions options1 = ActivityOptions.makeBasic();
        //判断是否冻结最近任务列表,前面传递的值是false
        if (freezeTaskList) {
            options1.setFreezeRecentTasksReordering();
        }
        
        //获取之前保存在SplitSelectDataHolder中的数据
        SplitSelectDataHolder.SplitLaunchData launchData =
                mSplitSelectDataHolder.getSplitLaunchData();
        int firstTaskId = launchData.getInitialTaskId();
        int secondTaskId = launchData.getSecondTaskId();
        ShortcutInfo firstShortcut = launchData.getInitialShortcut();
        ShortcutInfo secondShortcut = launchData.getSecondShortcut();
        PendingIntent firstPI = launchData.getInitialPendingIntent();
        PendingIntent secondPI = launchData.getSecondPendingIntent();
        int firstUserId = launchData.getInitialUserId();
        int secondUserId = launchData.getSecondUserId();
        int initialStagePosition = launchData.getInitialStagePosition();
        //获取基本的Bundle
        Bundle optionsBundle = options1.toBundle();
        //判断ENABLE_SHELL_TRANSITIONS
        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
            //获取远程动画
            final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
                    secondTaskId, callback);
            //根据launchData.getSplitLaunchType()的值选择SystemUiProxy调用哪个方法
            switch (launchData.getSplitLaunchType()) {
                case SPLIT_TASK_TASK ->
                        mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
                                null /* options2 */, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);

                case SPLIT_TASK_PENDINGINTENT ->
                        mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
                                firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);

                case SPLIT_TASK_SHORTCUT ->
                        mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
                                firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);

                case SPLIT_PENDINGINTENT_TASK ->
                        mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
                                secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);

                case SPLIT_PENDINGINTENT_PENDINGINTENT ->
                        mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
                                optionsBundle, secondPI, secondUserId, secondShortcut,
                                null /*options2*/, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);

                case SPLIT_SHORTCUT_TASK ->
                        mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
                                secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
                                remoteTransition, shellInstanceId);
            }
        } else {
            final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId,
                    callback);
            switch (launchData.getSplitLaunchType()) {
                case SPLIT_TASK_TASK ->
                        mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
                                secondTaskId, null /* options2 */, initialStagePosition,
                                splitRatio, adapter, shellInstanceId);

                case SPLIT_TASK_PENDINGINTENT ->
                        mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
                                secondUserId, optionsBundle, firstTaskId, null /*options2*/,
                                initialStagePosition, splitRatio, adapter, shellInstanceId);

                case SPLIT_TASK_SHORTCUT ->
                        mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
                                optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
                                splitRatio, adapter, shellInstanceId);

                case SPLIT_PENDINGINTENT_TASK ->
                        mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
                                optionsBundle, secondTaskId, null /*options2*/,
                                initialStagePosition, splitRatio, adapter, shellInstanceId);

                case SPLIT_PENDINGINTENT_PENDINGINTENT ->
                        mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
                                firstShortcut, optionsBundle, secondPI, secondUserId,
                                secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
                                adapter, shellInstanceId);

                case SPLIT_SHORTCUT_TASK ->
                        mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
                                optionsBundle, secondTaskId, null /*options2*/,
                                initialStagePosition, splitRatio, adapter, shellInstanceId);
            }
        }
    }

这个方法主要做了这几件事:
1.获取之前保存在SplitSelectDataHolder中的数据,上下分屏taskId、intent、分屏位置等等。
2.根据TaskAnimationManager.ENABLE_SHELL_TRANSITIONSlaunchData.getSplitLaunchType()的值来确定调用哪个方法。
3.通过SystemUiProxy跨进程调用到SystemUi进程

TaskAnimationManager.ENABLE_SHELL_TRANSITIONS默认值为true,我们只需要关注launchData.getSplitLaunchType()的值是多少即可。
代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt

    /**
     * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
     * Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
     */
    @VisibleForTesting
    @SplitLaunchType
    fun getSplitLaunchType(): Int {
        if (initialIntent != null || secondIntent != null) {
            throw IllegalStateException("Intents need to be converted")
        }

        // Prioritize task launches first
        if (initialTaskId != INVALID_TASK_ID) {
            if (secondTaskId != INVALID_TASK_ID) {
                return SPLIT_TASK_TASK
            }
            if (secondShortcut != null) {
                return SPLIT_TASK_SHORTCUT
            }
            if (secondPendingIntent != null) {
                return SPLIT_TASK_PENDINGINTENT
            }
        }
        ......
    }

这里initialTaskIdsecondTaskId均有值(前面有讲解保存到SplitSelectDataHolder中的流程),因此该方法返回值为SPLIT_TASK_TASK

最终我们知道其调用的是startTasks方法:

case SPLIT_TASK_TASK ->
        mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
                null /* options2 */, initialStagePosition, splitRatio,
                remoteTransition, shellInstanceId);

从参数中可以看到,我们主要传递的就是,需要分屏的两个taskId相关参数
firstTaskId, optionsBundle:第一个应用的TaskIdBundle对象
这里Bundle对象通过ActivityOptions获取

 final ActivityOptions options1 = ActivityOptions.makeBasic();
 Bundle optionsBundle = options1.toBundle();

可以添加打印看看里面最终是什么

for(String key : optionsBundle.keySet()){
    Slog.i("splitscreen","launchTasksRefactored"+
     " optionsBundle Key="+key + " value="+ optionsBundle.get(key)+"\n");

}

这里的打印的结果只有一对键值对:

Key=android.activity.splashScreenStyle value=-1

secondTaskId, null:第二个应用的TaskIdBundle对象(值为null)
initialStagePosition:分屏的初始位置
splitRatio:分屏的比例(值为前面launchSplitTasks方法传递的DEFAULT_SPLIT_RATIO,即0.5f)
remoteTransition:远程动画,可以为null,这是通过getShellRemoteTransition方法获取。
其中Bundle对象后续在SystemUI流程中会用到。

跨进程调用到SystemUI

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java

    /** Start multiple tasks in split-screen simultaneously. */
    public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
            @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
            RemoteTransition remoteTransition, InstanceId instanceId) {
        if (mSystemUiProxy != null) {
            try {
                mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
                        splitRatio, remoteTransition, instanceId);
            } catch (RemoteException e) {
                Log.w(TAG, "Failed call startTasks");
            }
        }
    }

其中mSplitScreenISplitScreen对象,ISplitScreen的实现在SplitScreenController.java中。

后续流程

Android U 多任务启动分屏——SystemUI流程

其他补充

ISystemUiProxy初始化

代码路径:packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java

	public static class TISBinder extends IOverviewProxy.Stub {
	
        ......

        @BinderThread
        public void onInitialize(Bundle bundle) {
            ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
            IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
            IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_BUBBLES));
            ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
                    KEY_EXTRA_SHELL_SPLIT_SCREEN));
            IOneHanded onehanded = IOneHanded.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
            IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
            IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
            ISysuiUnlockAnimationController launcherUnlockAnimationController =
                    ISysuiUnlockAnimationController.Stub.asInterface(
                            bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
            IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
            IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
            IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
            IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
            IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
                    bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
                SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
                        bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
                        recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
                        unfoldTransition, dragAndDrop);
                tis.initInputMonitor("TISBinder#onInitialize()");
                tis.preloadOverview(true /* fromInit */);
            }));
            sIsInitialized = true;
        }
        ......
    }

调用初始化的地方
代码路径:frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

    private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            ......
            mOverviewProxy = IOverviewProxy.Stub.asInterface(service);

            Bundle params = new Bundle();
            params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
            params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
            params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
            params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
                    mSysuiUnlockAnimationController.asBinder());
            mUnfoldTransitionProgressForwarder.ifPresent(
                    unfoldProgressForwarder -> params.putBinder(
                            KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
                            unfoldProgressForwarder.asBinder()));
            // Add all the interfaces exposed by the shell
            mShellInterface.createExternalInterfaces(params);
        }
        ......
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值