hi,粉丝朋友们!
@IntDef(prefix = { "WINDOWING_MODE_" }, value = {
WINDOWING_MODE_UNDEFINED,
WINDOWING_MODE_FULLSCREEN,
WINDOWING_MODE_MULTI_WINDOW,
WINDOWING_MODE_PINNED,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_FREEFORM,
})
今天来给大家介绍一下再Activity中WindowMode相关的多窗口模式,这个模式相对平时比较少见,但是在分屏模式之自由窗口模式,等存在多个窗口场景就很关键了。这一部分确实不是普通模式的场景,而且每一种模式都是比较难的,今天来分析一下WINDOWING_MODE_MULTI_WINDOW模式的一个疑问。
更多framework核心专题参考内容
https://ke.qq.com/course/package/83580?tuin=7d4eb354
疑问背景:
activity的WindowMode被设置成了WINDOWING_MODE_MULTI_WINDOW后,或者其他几个非WINDOWING_MODE_FULLSCREEN的WindowMode,为啥可以同一个画面显示多个Activity,而且多个Activity都是Resume的状态。
比如拿我们CarLauncher的地图画面来说:
具体可以看对应的am stack list看看Task和Activity相关的情况
test@test:~$ adb shell am stack list
RootTask id=1000098 bounds=[303,57][1200,658] displayId=0 userId=10
configuration={1.0 310mcc260mnc [en_US] ldltr sw1066dp w1196dp h801dp 120dpi xlrg land car finger qwerty/v/v -nav/h winConfig={ mBounds=Rect(303, 57 - 1200, 658) mAppBounds=Rect(303, 57 - 1200, 658) mMaxBounds=Rect(0, 0 - 1200, 800) mDisplayRotation=ROTATION_0 mWindowingMode=multi-window mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.29 fontWeightAdjustment=0}
taskId=1000098: com.android.car.mapsplaceholder/com.android.car.mapsplaceholder.MapsPlaceholderActivity bounds=[303,57][1200,658] userId=10 visible=true topActivity=ComponentInfo{com.android.car.mapsplaceholder/com.android.car.mapsplaceholder.MapsPlaceholderActivity}
RootTask id=1 bounds=[0,0][1200,800] displayId=0 userId=10
configuration={1.0 310mcc260mnc [en_US] ldltr sw1066dp w1600dp h1042dp 120dpi xlrg land car finger qwerty/v/v -nav/h winConfig={ mBounds=Rect(0, 0 - 1200, 800) mAppBounds=Rect(0, 0 - 1200, 800) mMaxBounds=Rect(0, 0 - 1200, 800) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=home mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.29 fontWeightAdjustment=0}
taskId=2: com.android.car.settings/com.android.car.settings.FallbackHome bounds=[0,0][1200,800] userId=0 visible=true topActivity=ComponentInfo{com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher}
taskId=1000095: com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher bounds=[0,0][1200,800] userId=10 visible=true topActivity=ComponentInfo{com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher}
可以看到其实我们桌面是CarLauncher这个Activity,它是覆盖全屏幕,但是发现居然有个MapsPlaceholderActivity的Activity还在它的上面,显示区域Rect(303, 57 - 1200, 658)就是图片绿色部分,属于地图放置部分
通过dumpsys activity activities可以看出MapsPlaceholderActivity ,CarLauncher两个Activity都属于state=RESUMED状态
大家如果正常理解是不是惊呆了?为啥有两个Activity都是Resumed,正常启动新的Activity时候是不是之前Activity就要Paused,那么这个是在框架怎么实现的不会对之前的Activity进行Paused
解答:
需要解答该问题,其实可以从Activity的是怎么被pause地方进行入手
以前讲解过进入pasue主要pauseBackTasks方法进行
boolean pauseBackTasks(ActivityRecord resuming) {
//省略
leafTask.forAllLeafTaskFragments((taskFrag) -> {
final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
someActivityPaused[0]++;
}
}
}, true /* traverseTopToBottom */);
}, true /* traverseTopToBottom */);
return someActivityPaused[0] > 0;
}
具体可以看下面堆栈:
05-16 22:55:01.987 633 1162 I WindowManager: java.lang.Exception
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.TaskFragment.resumeTopActivity(TaskFragment.java:1205)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.Task.resumeTopActivityInnerLocked(Task.java:5003)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:4938)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2260)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityStarter.startActivityInner(ActivityStarter.java:1935)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1661)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityStarter.executeRequest(ActivityStarter.java:1216)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:702)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityStartController.startActivityInPackage(ActivityStartController.java:283)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.wm.ActivityTaskManagerService$LocalService.startActivityInPackage(ActivityTaskManagerService.java:5445)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:483)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:309)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:5384)
05-16 22:55:01.987 633 1162 I WindowManager: at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:4402)
05-16 22:55:01.987 633 1162 I WindowManager: at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2638)
05-16 22:55:01.987 633 1162 I WindowManager: at android.os.Binder.execTransactInternal(Binder.java:1280)
05-16 22:55:01.987 633 1162 I WindowManager: at android.os.Binder.execTransact(Binder.java:1244)
如果是正常模式肯定就会进入Pasue,但是WINDOWING_MODE_MULTI_WINDOW不会进入,这个是为啥?那就要具体分析怎么才可以进入这个taskFrag.startPausing的条件:
if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
someActivityPaused[0]++;
}
}
这个地方有resumedActivity这个一般就是Carauncher,
resuming就是我们的MapsPlaceholderActivity,
taskFrag就是Carauncher的Task
taskFrag.canBeResumed代表就是这个taskFrag是否可以处于Resumed状态
如果为true则不可以pasue之前activity,如果为false就是要pause前面Activity(CarLauncher)
boolean canBeResumed(@Nullable ActivityRecord starting) {
// No need to resume activity in TaskFragment that is not visible.
return isTopActivityFocusable()
&& getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
}
canBeResumed的判断又是对Task的isTopActivityFocusable()和getVisibility(starting)进行判断,这里isTopActivityFocusable一般都为true,getVisibility是关键,也就是再不进行pause
下面重点看getVisibility实现:
int getVisibility(ActivityRecord starting) {
//省略部分
if (parent.asTaskFragment() != null) {
//省略部分
final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer other = parent.getChildAt(i);//遍历TaskDisplayarea所有子节点,赋值other
if (other == null) continue;
final boolean hasRunningActivities = hasRunningActivity(other);
if (other == this) {//这里条件是要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.
//这个地方本身有CarLauncher这个hasRunningActivities满足为true,所以shouldBeVisible为true
shouldBeVisible = hasRunningActivities
|| (starting != null && starting.isDescendantOf(this))
|| isActivityTypeHome();
break;
}
//但是CarLauncher的Task在MapsPlaceholderActivity的底部,所以先遍历到MapsPlaceholderActivity的Task
final int otherWindowingMode = other.getWindowingMode();
//关键判断MapsPlaceholderActivity的task的WindowingMode是否属于WINDOWING_MODE_FULLSCREEN,如果属于这里就会return INVISIBLE,这里也就是之前会导致pause的关键,但是因为我们设置成了WINDOWING_MODE_MULTI_WINDOW,所以无法进入这个里面
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()) {
//这里虽然判断了WINDOWING_MODE_MULTI_WINDOW,但是因为MapsPlaceholderActivity的bound区域不是全屏,所以也不会进入这里进行INVISIBLE
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;
}
代码较多较复杂,看一两次很难看懂的总结一下:
Visible变量影响关键
1、遍历当前的所有Task,根据先后顺序,一般先遍历的Task肯定是最先的MapsPlaceholderActivity的Task
2、导致会进行Pause关键是如果MapsPlaceholderActivity的Task是WINDOWING_MODE_FULLSCREEN或WINDOWING_MODE_MULTI_WINDOW但是一定要可以覆盖全部下面的Task,说白了就是如果上面Task会把自己覆盖那么就一定会导致INVISIBLE
3、一旦不会覆盖,那么自己Task有正在执行Activity,那么就会返回VISIBLE
具体图如下: