Android U 自由窗口(浮窗)——补充

生命周期相关

这里还是以多任务中启动自由窗口为例,对比与桌面侧直接启动应用的生命周期

桌面侧直接启动应用的生命周期

java 复制代码
1181  1181 I wm_on_top_resumed_lost_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed]
1181  1181 I wm_on_paused_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,performPause,0]
2765  2765 I wm_on_create_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,performCreate,129]
2765  2765 I wm_on_start_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,handleStartActivity,1]
2765  2765 I wm_on_resume_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,RESUME_ACTIVITY,17]
2765  2765 I wm_on_top_resumed_gained_called: [227340519,com.android.messaging.ui.conversationlist.ConversationListActivity,topStateChangedWhenResumed]
1181  1181 I wm_on_stop_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,STOP_ACTIVITY_ITEM,6]

从多任务启动自由窗口生命周期

java 复制代码
//进入多任务
1181  1181 I wm_on_restart_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,performRestart,0]
1181  1181 I wm_on_start_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,handleStartActivity,10]
1181  1181 I wm_on_resume_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,RESUME_ACTIVITY,11]
1181  1181 I wm_on_top_resumed_gained_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed]

//启动自由窗口
2073  2073 I wm_on_paused_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performPause,1]
1181  1181 I wm_on_top_resumed_lost_called: [235136609,com.android.launcher3.uioverrides.QuickstepLauncher,topStateChangedWhenResumed]
2073  2073 I wm_on_stop_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,handleRelaunchActivity,0]
2073  2073 I wm_on_destroy_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performDestroy,0]
2073  2073 I wm_on_create_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,performCreate,34]
2073  2073 I wm_on_start_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,handleStartActivity,1]
2073  2073 I wm_on_resume_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,RESUME_ACTIVITY,28]
2073  2073 I wm_on_top_resumed_gained_called: [162074025,com.android.messaging.ui.conversationlist.ConversationListActivity,topStateChangedWhenResumed]

对比直接从桌面侧启动应用时的生命周期流程存在差异,即启动自由窗口时底层activity(launcher3)没有进行pause

一般来说当一个应用启动,那么当前应用就会走pause流程,我们这里launcher3为什么没有走pause呢?

其实自由窗口的启动本质上来说就是启动一个具有特殊属性activity,因此我们需要回顾activity的启动流程中桌面的pause是如何确定的。

resumeTopActivity中pause流程分析

代码路径:frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java

java 复制代码
    final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
            ......
            boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);

先看这个resume流程,这会去判断是否需要pause。

这里deferPause的值是通过Task.resumeTopActivityUncheckedLocked方法中传递过来的,其值为false,因此!deferPause为true。
next指的是要启动的activity(我们这里是ConversationListActivity)。

代码路径:frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java

java 复制代码
    /**
     * Pause all activities in either all of the root tasks or just the back root tasks. This is
     * done before resuming a new activity and to make sure that previously active activities are
     * paused in root tasks that are no longer visible or in pinned windowing mode. This does not
     * pause activities in visible root tasks, so if an activity is launched within the same root
     * task, hen we should explicitly pause that root task's top activity.
     *
     * @param resuming    The resuming activity.
     * @return {@code true} if any activity was paused as a result of this call.
     */
    boolean pauseBackTasks(ActivityRecord resuming) {
        final int[] someActivityPaused = {0};
        forAllLeafTasks(leafTask -> {
            // Check if the direct child resumed activity in the leaf task needed to be paused if
            // the leaf task is not a leaf task fragment.
            //task fragment场景,暂不关注
            if (!leafTask.isLeafTaskFragment()) {
                final ActivityRecord top = topRunningActivity();
                final ActivityRecord resumedActivity = leafTask.getResumedActivity();
                if (resumedActivity != null && top.getTaskFragment() != leafTask) {
                    // Pausing the resumed activity because it is occluded by other task fragment.
                    if (leafTask.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
                        someActivityPaused[0]++;
                    }
                }
            }
            
            //遍历所有leafTask节点,即最底端Task节点
            leafTask.forAllLeafTaskFragments((taskFrag) -> {
                final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
                //判断当前taskFrag是否有存在resumed的Activity,并且即将启动的Activity不能resumed
                if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
                     //暂停当前存在resumed的Activity
                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
                        //记录暂停的Activity
                        someActivityPaused[0]++;
                    }
                }
            }, true /* traverseTopToBottom */);
        }, true /* traverseTopToBottom */);
        return someActivityPaused[0] > 0;
    }

这个方法会遍历TaskDisplayArea下的所有leafTask节点,即最底端Task节点

  1. 判断当前Task的Activity是否存在resumed状态,且即将启动的Activity不能被resumed
  2. 暂停当前存在resumed的Activity,若暂停成功,给参数someActivityPaused计数

如下图圈的task节点是会遍历的task节点。

在该界面当我们点击启动自由窗口后开始条件判断resumedActivity != null && !taskFrag.canBeResumed(resuming)
taskFrag.getResumedActivity()获取此时是resumed状态的task,当遍历到桌面Task时,由于我们要从多任务进入到自由窗口,而此时多任务界面为resumed状态,存在resumed状态的Activity,因此参数resumedActivity不为空。

这里resumedActivity != null为true,再来看看!taskFrag.canBeResumed(resuming)其中参数resuming指的是即将启动的Activity(ConversationListActivity)。

canBeResumed

代码路径:frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java

java 复制代码
    /**
     * Returns {@code true} is the activity in this TaskFragment can be resumed.
     *
     * @param starting The currently starting activity or {@code null} if there is none.
     */
    boolean canBeResumed(@Nullable ActivityRecord starting) {
        // No need to resume activity in TaskFragment that is not visible.
        return isTopActivityFocusable()
                && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

isTopActivityFocusable()判断顶部Activity是否可聚焦,一般为true,这里主要关注getVisibility(starting)方法,通样传递starting即将启动的的Activity (ConversationListActivity)。

java 复制代码
    /**
     * Returns the visibility state of this TaskFragment.
     *
     * @param starting The currently starting activity or null if there is none.
     */
    @TaskFragmentVisibility
    int getVisibility(ActivityRecord starting) {
        ......
        //根据上述代码流程,这里this指的是桌面task,因此此时getParent获取的是桌面task的task
        final WindowContainer<?> parent = getParent();
        ......
        // This TaskFragment is only considered visible if all its parent TaskFragments are
        // considered visible, so check the visibility of all ancestor TaskFragment first.
        
        //判断parent是否是task或者TaskFragment
        if (parent.asTaskFragment() != null) {
            //递归调用getVisibility,直到parent为TaskDisplayArea(不是task)为止。
            //其目的是为了能够遍历到TaskDisplayArea下所有task节点
            final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
            if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
                // Can't be visible if parent isn't visible
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            } else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
                // Parent is behind a translucent container so the highest visibility this container
                // can get is that.
                gotTranslucentFullscreen = true;
            }
        }
        
        final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
        //逆序遍历parent下的task,即优先最前台,从大到小遍历
        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
            final WindowContainer other = parent.getChildAt(i);
            if (other == null) continue;

            //判断other是否是运行中的Activity
            final boolean hasRunningActivities = hasRunningActivity(other);
            //other和this相同则中断循环,做后续处理
            if (other == this) {
                ......
                // Should be visible if there is no other fragment occluding it, unless it doesn't
                // have any running activities, not starting one and not home stack.
                shouldBeVisible = hasRunningActivities
                        || (starting != null && starting.isDescendantOf(this))
                        || isActivityTypeHome();
                break;
            }

            if (!hasRunningActivities) {
                continue;
            }

            //根据other的WindowingMode判断返回值
            final int otherWindowingMode = other.getWindowingMode();
            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
                if (isTranslucent(other, starting)) {
                    // Can be visible behind a translucent fullscreen TaskFragment.
                    gotTranslucentFullscreen = true;
                    continue;
                }
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
                    && other.matchParentBounds()) {
                if (isTranslucent(other, starting)) {
                    // Can be visible behind a translucent TaskFragment.
                    gotTranslucentFullscreen = true;
                    continue;
                }
                // Multi-window TaskFragment that matches parent bounds would occlude other children
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            }

            ......

        }
        
        if (!shouldBeVisible) {
            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
        }
        
        // Lastly - check if there is a translucent fullscreen TaskFragment on top.
        return gotTranslucentFullscreen
                ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
                : TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

简单来说这个方法就是判断当前启动之后的activity的WindowingMode如果不是 WINDOWING_MODE_FULLSCREEN或者WINDOWING_MODE_MULTI_WINDOW那么就返回TASK_FRAGMENT_VISIBILITY_VISIBLE,使后台应用可见,不进入到pause流程。

因此启动自由窗口时底层activity(launcher3)不会进行pause。

保持顶部显示方法

当我们启动了一个自由窗口后,即使启动了其他的应用也希望这个窗口一直保持在顶部。

回顾自由窗口启动流程中桌面侧会设置相应的ActivityOptions给需要启动为自由窗口activity。

activityOptions在Launcher侧设置

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

java 复制代码
    class FreeformSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
        ......
        private ActivityOptions makeLaunchOptions(Activity activity) {
            ActivityOptions activityOptions = ActivityOptions.makeBasic();
            activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
            // Arbitrary bounds only because freeform is in dev mode right now
            final View decorView = activity.getWindow().getDecorView();
            final WindowInsets insets = decorView.getRootWindowInsets();
            final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2);
            r.offsetTo(insets.getSystemWindowInsetLeft() + 50,
                    insets.getSystemWindowInsetTop() + 50);
            activityOptions.setLaunchBounds(r);
            /* 添加保持在顶部的options start */
            activityOptions.setTaskAlwaysOnTop(true);
            /* end */
            return activityOptions;
        }
    }

修改后发现进入到自由窗口之后,启动其他应用仍然会被覆盖,因此我们需要确认activityOptions.setTaskAlwaysOnTop(true);在桌面侧设置的参数是否在system_server端生效。

activityOptions在system_server侧的设置

回顾TaskDisplayArea.getOrCreateRootTask方法

java 复制代码
    /**
     * When two level tasks are required for given windowing mode and activity type, returns an
     * existing compatible root task or creates a new one.
     * For one level task, the candidate task would be reused to also be the root task or create
     * a new root task if no candidate task.
     *
     * @param windowingMode The windowing mode the root task should be created in.
     * @param activityType  The activityType the root task should be created in.
     * @param onTop         If true the root task will be created at the top of the display,
     *                      else at the bottom.
     * @param candidateTask The possible task the activity might be launched in. Can be null.
     * @param sourceTask    The task requesting to start activity. Used to determine which of the
     *                      adjacent roots should be launch root of the new task. Can be null.
     * @param options       The activity options used to the launch. Can be null.
     * @param launchFlags   The launch flags for this launch.
     * @return The root task to use for the launch.
     * @see #getRootTask(int, int)
     */
    Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
            @Nullable Task candidateTask, @Nullable Task sourceTask,
            @Nullable ActivityOptions options, int launchFlags) {
        //我们这里windowingMode传递的是WINDOWING_MODE_FREEFORM,
        //因此resolvedWindowingMode为WINDOWING_MODE_FREEFORM
        final int resolvedWindowingMode =
                windowingMode == WINDOWING_MODE_UNDEFINED ? getWindowingMode() : windowingMode;
        // Need to pass in a determined windowing mode to see if a new root task should be created,
        // so use its parent's windowing mode if it is undefined.
        //我们这里resolvedWindowingMode是WINDOWING_MODE_FREEFORM
        //activityType是ACTIVITY_TYPE_STANDARD,因此alwaysCreateRootTask为true
        //这取反为false,因此不进入该逻辑
        if (!alwaysCreateRootTask(resolvedWindowingMode, activityType)) {
            Task rootTask = getRootTask(resolvedWindowingMode, activityType);
            if (rootTask != null) {
                return rootTask;
            }
        } else if (candidateTask != null) {
            final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
            //这里getLaunchRootTask是同options获取,我们这里options没有设置过mLaunchRootTask,因此为null
            final Task launchParentTask = getLaunchRootTask(resolvedWindowingMode, activityType,
                    options, sourceTask, launchFlags, candidateTask);
            //launchParentTask为null不进入该流程
            if (launchParentTask != null) {
                if (candidateTask.getParent() == null) {
                    launchParentTask.addChild(candidateTask, position);
                } else if (candidateTask.getParent() != launchParentTask) {
                    candidateTask.reparent(launchParentTask, position);
                }
            //candidateTask.getDisplayArea()和this都是默认的TaskDisplayArea,即DefaultTaskDisplayArea
            //两者相同,即candidateTask.getDisplayArea() != this 为false,因此不进入该流程
            } else if (candidateTask.getDisplayArea() != this 
                    || candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {
                if (candidateTask.getParent() == null) {
                    addChild(candidateTask, position);
                } else {
                    candidateTask.reparent(this, onTop);
                }
            }
            // Update windowing mode if necessary, e.g. launch into a different windowing mode.
            //windowingMode传递的是WINDOWING_MODE_FREEFORM
            //candidateTask自身就是rootTask
            //candidateTask前后windowingMode有发生变化
            if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                    && candidateTask.getWindowingMode() != windowingMode) {
                //ShellTransitions调用collect处理
                candidateTask.mTransitionController.collect(candidateTask);
                //设置task窗口模式
                candidateTask.setWindowingMode(windowingMode);
            }
            //返回candidateTask的rootTask
            return candidateTask.getRootTask();
        }
        return new Task.Builder(mAtmService)
                .setWindowingMode(windowingMode)
                .setActivityType(activityType)
                .setOnTop(onTop)
                .setParent(this)
                .setSourceTask(sourceTask)
                .setActivityOptions(options)
                .setLaunchFlags(launchFlags)
                .build();
    }

在自由窗口启动流程中,我们讲到,其最主要的操作在该流程中进行。

java 复制代码
            if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                    && candidateTask.getWindowingMode() != windowingMode) {
                candidateTask.mTransitionController.collect(candidateTask);
                candidateTask.setWindowingMode(windowingMode);
            }
            return candidateTask.getRootTask();

但是从代码中我们可以看到,这里并没有进行给task设置AlwaysOnTop,所以不会在最顶端显示。

修改

方案一:直接在上述代码中添加设置AlwaysOnTop

java 复制代码
            if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
                    && candidateTask.getWindowingMode() != windowingMode) {
                candidateTask.mTransitionController.collect(candidateTask);
                candidateTask.setWindowingMode(windowingMode);
                /* 添加保持在顶部的options start */
                candidateTask.setTaskAlwaysOnTop(true);
                /* end */
            }
            return candidateTask.getRootTask();

这种改法可以把桌面端设置TaskAlwaysOnTop给去掉,但是比较局限,不太推荐

方案二:在最开始调用anyTaskForId方法获取task的位置修改

java 复制代码
    int startActivityFromRecents(int callingPid, int callingUid, int taskId,
            SafeActivityOptions options) {
        ......
        synchronized (mService.mGlobalLock) {
            ......
            try {
                task = mRootWindowContainer.anyTaskForId(taskId,
                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
                /* 添加保持在顶部的options start */
                //判断task是否是自由窗口模式
                if(task.inFreeformWindowingMode()){
                    //获取桌面侧设置的TaskAlwaysOnTop状态
                    task.setAlwaysOnTop(activityOptions.getTaskAlwaysOnTop());
                }
                /* end */
 
                if (task == null) {
                    mWindowManager.executeAppTransition();
                    throw new IllegalArgumentException(
                            "startActivityFromRecents: Task " + taskId + " not found.");
                }

                if (moveHomeTaskForward) {
                    // We always want to return to the home activity instead of the recents
                    // activity from whatever is started from the recents activity, so move
                    // the home root task forward.
                    // TODO (b/115289124): Multi-display supports for recents.
                    mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeRootTaskToFront(
                            "startActivityFromRecents");
                }
                ......

这个改法比较推荐,在anyTaskForId整个流程完成之后再进行判断设置,比较稳健。

相关推荐
4Forsee2 小时前
【增强现实】快速上手 Vuforia Unity Android AR 应用开发
android·unity·ar
2501_944521592 小时前
Flutter for OpenHarmony 微动漫App实战:列表项组件实现
android·开发语言·javascript·flutter·ecmascript
小风呼呼吹儿2 小时前
Flutter 框架跨平台鸿蒙开发 - 种子发芽记录器:记录植物成长的每一刻
android·flutter·华为·harmonyos
一起养小猫2 小时前
Flutter for OpenHarmony 实战:Dart类与面向对象编程
android·flutter
2501_944526423 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 多语言国际化实现
android·java·开发语言·javascript·flutter·游戏
2501_916007473 小时前
iOS APP 开发,从项目创建、证书与描述文件配置、安装测试和IPA 上传
android·ios·小程序·https·uni-app·iphone·webview
AC赳赳老秦3 小时前
Docker+DeepSeek:生成镜像优化Dockerfile与容器健康检查脚本
android·运维·人工智能·机器学习·docker·容器·deepseek
2501_944521593 小时前
Flutter for OpenHarmony 微动漫App实战:骨架屏加载实现
android·开发语言·javascript·数据库·redis·flutter·缓存
summerkissyou19873 小时前
Android13-蓝牙-常见问题
android·蓝牙