AndroidQ 分屏窗口模式 (AMS部分)

article/2025/9/15 17:28:32

1. 多窗口

1.1 栈

Android7.0开始支持多窗口,多窗口分为三种,画中画,分屏,自由窗口,多窗口的核心原理其实就是分栈和设置栈边界,

分栈即把不同窗口模式下的Activity放在不同的ActivityStack中,Android7.0为区分不同ActivityStack定义了不同stackId:

        /** First static stack ID. */public static final int FIRST_STATIC_STACK_ID = 0;/** Home activity stack ID. */public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;/** ID of stack where fullscreen activities are normally launched into. */public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;/** ID of stack where freeform/resized activities are normally launched into. */public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;/** ID of stack that occupies a dedicated region of the screen. */public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;/** ID of stack that always on top (always visible) when it exist. */public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;

但在Android10.0中已经不是用stackId来分栈了,而是使用ActivityStack父类提供的getWindowingMode()方法来获取窗口模式进而区分不同ActivityStack,具体的windowingMode定义在WindowConfiguration中:

 /** Windowing mode is currently not defined. */public static final int WINDOWING_MODE_UNDEFINED = 0;//普通全屏窗口public static final int WINDOWING_MODE_FULLSCREEN = 1;//画中画public static final int WINDOWING_MODE_PINNED = 2;//分屏主窗口public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;//分屏副窗口public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;/*** Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage* points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container* will launch into fullscreen or split-screen secondary depending on if the device is currently* in fullscreen mode or split-screen mode.*/public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY =WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;//自有窗口public static final int WINDOWING_MODE_FREEFORM = 5;

1.2 Android对Activity的组织方式

Android对Activity的组织方式通过容器分级存储,容器分为AMS侧和WMS侧,AMS这边顶级容器为ConfigurationContainer,WMS这边顶级容器可以简单认为是WindowContainer(WindowContainer其实也是继承ConfigurationContainer的),接着AMS这边依次为:

ActivityDisplay->ActivityStack->TaskRecord->ActivityRecord

WMS这边仅针对Activity这种类型窗口来说也可以依次分为:

DisplayContent(TaskStackContainer)->TaskStack->Task->AppWindowToken

这种分级存储有利于对Activity的管理,AMS这边管理的是Activity,WMS这边管理的是Window.

1.2 栈边界

不同的窗口模式有不同的ActivityStack,而不同的ActivityStack有自己的边界,ActivityStack中的Activity只能显示在ActivityStack边界内,所以只要设置好ActivityStack的边界,其内部的Activity大小就确定了,设置边界的核心是setBounds方法:

  //  ActivityStack.java@Overridepublic int setBounds(Rect bounds) {return super.setBounds(!inMultiWindowMode() ? null : bounds);}

可以看到在设置ActivityStack栈边界时会有判断,只有当前ActivityStack处于多窗口模式时才能设置边界:

   //ConfigurationContainer.java/*** Returns true if this container is currently in multi-window mode. I.e. sharing the screen* with another activity.*/public boolean inMultiWindowMode() {/*@WindowConfiguration.WindowingMode*/ int windowingMode =mFullConfiguration.windowConfiguration.getWindowingMode();return windowingMode != WINDOWING_MODE_FULLSCREEN&& windowingMode != WINDOWING_MODE_UNDEFINED;}

2. 分屏模式

2.1 进入分屏

Android原生的分屏入口在SystemUI的最近任务界面,对于车机来说没有最近任务界面,但其实要进去分屏模式非常简单,前面说了多窗口核心原理其实就是分栈和设置栈边界,所以要进去分屏只需要将当前应用所在的ActivityStack设置为分屏的栈,即设置其windowmodeWINDOWING_MODE_SPLIT_SCREEN_PRIMARY,然后设置其分屏之后的栈的边界就行了,核心方法两个,ActivityTaskManagerService中的setTaskWindowingModeSplitScreenPrimaryresizeDockedStack,这两个方法是支持Binder调用的,SystemUI就是用的这两个方法,我这边直接在Framework中调用之后测试结果如下:
在这里插入图片描述
很简单就实现了分屏。

2.2 设置分屏模式

 /**参数:taskId:进入分屏的Activity所在的task的IDcreateMode:进入分屏的Activity显示在上面还是下面,如果是横屏则是左边或者右边initialBounds:进入分屏的Activity的大小toTop:进入分屏的Activity是否移动到顶部*/@Overridepublic boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,boolean toTop, boolean animate, Rect initialBounds, boolean showRecents) {enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,"setTaskWindowingModeSplitScreenPrimary()");synchronized (mGlobalLock) {final long ident = Binder.clearCallingIdentity();try {//根据taskId拿到TaskRecordfinal TaskRecord task = mRootActivityContainer.anyTaskForId(taskId,MATCH_TASK_IN_STACKS_ONLY);if (task == null) {Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);return false;}if (DEBUG_STACK) Slog.d(TAG_STACK,"setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId+ " to createMode=" + createMode + " toTop=" + toTop);if (!task.isActivityTypeStandardOrUndefined()) {throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"+ " non-standard task " + taskId + " to split-screen windowing mode");}mWindowManager.setDockedStackCreateState(createMode, initialBounds);final int windowingMode = task.getWindowingMode();final ActivityStack stack = task.getStack();if (toTop) {//将Activity所在的task所在的ActivityStack移到最顶部stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);}//核心:设置当前ActivityStack的windowMode为WINDOWING_MODE_SPLIT_SCREEN_PRIMARY//这里进入分屏的应用并不会新建栈,而是复用当前栈,然后修改栈的windowMode就行了,对比画中画//和自由窗口是不一样的。stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents,false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,false /* creating */);return windowingMode != task.getWindowingMode();} finally {Binder.restoreCallingIdentity(ident);}}}

setTaskWindowingModeSplitScreenPrimary方法核心其实就是将当前要进入分屏的应用所在的ActivityStack的windowMode修改为WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,修改了模式后面在设置栈边界时才能生效。

2.3 设置栈边界

    @Overridepublic void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,Rect tempDockedTaskInsetBounds,Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");long ident = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {mStackSupervisor.resizeDockedStackLocked(dockedBounds, tempDockedTaskBounds,tempDockedTaskInsetBounds, tempOtherTaskBounds, tempOtherTaskInsetBounds,PRESERVE_WINDOWS);}} finally {Binder.restoreCallingIdentity(ident);}}

ActivityStackSupervisor.resizeDockedStackLocked:

  void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,boolean preserveWindows) {resizeDockedStackLocked(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,tempOtherTaskBounds, tempOtherTaskInsetBounds, preserveWindows,false /* deferResume */);}

resizeDockedStackLocked:

void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,boolean preserveWindows, boolean deferResume) {//是否允许StackResizeif (!mAllowDockedStackResize) {// Docked stack resize currently disabled.return;}//获取当前系统中处于分屏模式的ActivityStackfinal ActivityStack stack =mRootActivityContainer.getDefaultDisplay().getSplitScreenPrimaryStack();if (stack == null) {Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");return;}if (mDockedStackResizing) {mHasPendingDockedBounds = true;mPendingDockedBounds = copyOrNull(dockedBounds);mPendingTempDockedTaskBounds = copyOrNull(tempDockedTaskBounds);mPendingTempDockedTaskInsetBounds = copyOrNull(tempDockedTaskInsetBounds);mPendingTempOtherTaskBounds = copyOrNull(tempOtherTaskBounds);mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds);}Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");//通知WMS延迟布局mWindowManager.deferSurfaceLayout();try {// 防止重复调用mAllowDockedStackResize = false;//获取分屏ActivityStack中的顶部ActivityActivityRecord r = stack.topRunningActivityLocked();//设置ActivityStack大小stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN|| (dockedBounds == null && !stack.isAttached())) {moveTasksToFullscreenStackLocked(stack, ON_TOP);// stack shouldn't contain anymore activities, so nothing to resume.r = null;} else {//遍历Display下所有ActivityStack,final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();final Rect otherTaskRect = new Rect();for (int i = display.getChildCount() - 1; i >= 0; --i) {final ActivityStack current = display.getChildAt(i);if (!current.inSplitScreenSecondaryWindowingMode()) {continue;}if (!current.affectedBySplitScreenResize()) {continue;}if (mDockedStackResizing && !current.isTopActivityVisible()) {// Non-visible stacks get resized once we're done with the resize// interaction.continue;}//对分屏副窗口计算大小以及位置,位置分为上下,横屏为左右current.getStackDockedModeBounds(dockedBounds,tempOtherTaskBounds /* currentTempTaskBounds */,tempRect /* outStackBounds */,otherTaskRect /* outTempTaskBounds */);mRootActivityContainer.resizeStack(current,!tempRect.isEmpty() ? tempRect : null,!otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,tempOtherTaskInsetBounds, preserveWindows,true /* allowResizeInDockedMode */, deferResume);}}if (!deferResume) {stack.ensureVisibleActivitiesConfigurationLocked(r, preserveWindows);}} finally {mAllowDockedStackResize = true;mWindowManager.continueSurfaceLayout();Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}}

重点看分屏副窗口的大小计算,分屏主窗口大小一般由调用者提供,

getStackDockedModeBounds:

void getStackDockedModeBounds(Rect dockedBounds, Rect currentTempTaskBounds,Rect outStackBounds, Rect outTempTaskBounds) {if (mTaskStack != null) {mTaskStack.getStackDockedModeBoundsLocked(getParent().getConfiguration(), dockedBounds,currentTempTaskBounds, outStackBounds, outTempTaskBounds);} else {outStackBounds.setEmpty();outTempTaskBounds.setEmpty();}}

TaskStack.getStackDockedModeBoundsLocked:

void getStackDockedModeBoundsLocked(Configuration parentConfig, Rect dockedBounds,Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds) {outTempTaskBounds.setEmpty();if (dockedBounds == null || dockedBounds.isEmpty()) {// Calculate the primary docked bounds.final boolean dockedOnTopOrLeft = mWmService.mDockedStackCreateMode== SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;getStackDockedModeBounds(parentConfig,true /* primary */, outStackBounds, dockedBounds,mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);return;}//计算分屏副窗口的位置,上或者下,横屏左或者右,计算规则比较简单,首先判断当前设备处于横屏还是竖屏,//横屏则比较屏幕和分屏主窗口x坐标,竖屏则比较屏幕和分屏主窗口y坐标final int dockedSide = getDockSide(parentConfig, dockedBounds);......final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;//计算分屏副窗口边界大小getStackDockedModeBounds(parentConfig,false /* primary */, outStackBounds, dockedBounds,mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);}

计算分屏副窗口边界方法getStackDockedModeBounds中传了个很重要的参数, mDisplayContent.mDividerControllerLocked.getContentWidth()这个参数得到的是分屏中间的DividerView的宽度,就是那条分割屏幕的线,由下面方法计算得到:

 int getContentWidth() {return mDividerWindowWidth - 2 * mDividerInsets;}
mDividerWindowWidth = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_thickness);mDividerInsets = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);

mDividerWindowWidthmDividerInsets定义在frameworks/base/core/res/res/values/dimens.xml中,调整DividerView的宽度测试效果如下:
在这里插入图片描述

TaskStack.getStackDockedModeBounds:

private void getStackDockedModeBounds(Configuration parentConfig, boolean primary,Rect outBounds, Rect dockedBounds, int dockDividerWidth,boolean dockOnTopOrLeft) {//参数parentConfig是Display的配置,所以这里getBounds得到的是屏幕的边界(Rect(400, 0 - 800, 400))final Rect displayRect = parentConfig.windowConfiguration.getBounds();//是否水平分屏final boolean splitHorizontally = displayRect.width() > displayRect.height();outBounds.set(displayRect);//分屏主窗口if (primary) {......return;}// 分屏主窗口不在上或者左,if (!dockOnTopOrLeft) {//横屏分屏if (splitHorizontally) {//分屏副窗口right = 分屏主窗口的left减去分屏分割线的宽度outBounds.right = dockedBounds.left - dockDividerWidth;} else {//竖屏分屏//分屏副窗口bottom = 分屏主窗口的top减去分屏分割线的宽度outBounds.bottom = dockedBounds.top - dockDividerWidth;}} else {//分屏主窗口在上或者左,计算方法类似if (splitHorizontally) {outBounds.left = dockedBounds.right + dockDividerWidth;} else {outBounds.top = dockedBounds.bottom + dockDividerWidth;}}//确认计算得到的边界是有效的DockedDividerUtils.sanitizeStackBounds(outBounds, !dockOnTopOrLeft);}

分屏副窗口的边界计算很简单,核心就是分屏主窗口与分屏分割线做加减。

分屏副窗口边界计算完成之后,会调用RootActivityContainerresizeStack方法设置边界:

RootActivityContainer.resizeStack:

void resizeStack(ActivityStack stack, Rect bounds, Rect tempTaskBounds,Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,boolean deferResume) {//stack是否是分屏主窗口if (stack.inSplitScreenPrimaryWindowingMode()) {mStackSupervisor.resizeDockedStackLocked(bounds, tempTaskBounds,tempTaskInsetBounds, null, null, preserveWindows, deferResume);return;}//系统当前是否存在分屏栈final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();//基本判断,是否允许栈Resizeif (!allowResizeInDockedMode&& !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {return;}//延迟布局mWindowManager.deferSurfaceLayout();try {if (stack.affectedBySplitScreenResize()) {if (bounds == null && stack.inSplitScreenWindowingMode()) {// 计算出来的分屏副窗口边界为空的情况则将分屏副窗口所在的栈windowMode设置为WINDOWING_MODE_FULLSCREENstack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);} else if (splitScreenActive) {//当前处于分屏状态则将分屏副窗口的windowMode设置为WINDOWING_MODE_SPLIT_SCREEN_SECONDARYstack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);}}//将之前计算出来的分屏副窗口边界保存下来,Rect(0, 0 - 360, 480)stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);if (!deferResume) {stack.ensureVisibleActivitiesConfigurationLocked(stack.topRunningActivityLocked(), preserveWindows);}} finally {mWindowManager.continueSurfaceLayout();Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}}

ActivityStack.resize:

 void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {if (!updateBoundsAllowed(bounds)) {return;}// Update override configurations of all tasks in the stack.final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;for (int i = mTaskHistory.size() - 1; i >= 0; i--) {final TaskRecord task = mTaskHistory.get(i);if (task.isResizeable()) {task.updateOverrideConfiguration(taskBounds, tempTaskInsetBounds);}}//核心:保存栈的边界setBounds(bounds);}

setBounds是AMS和WMS容器的顶级父类ConfigurationContainer中的方法,最终的边界值会保存到WindowConfiguration中,从类名可以看出来这个类其实是来存储窗口的配置信息的,包括之前的WindowMode也是保存在这里。

2.4 分屏Activity Resume

分屏主窗口和分屏副窗口的边界计算完成之后,会更新分屏主窗口Activity的Configuration,然后让分屏主窗口所在的栈的顶部Activity resume,重点看resume部分:

RootActivityContainer.resumeFocusedStacksTopActivities:

//分屏流程上调这个方法时传的三个参数都为null
boolean resumeFocusedStacksTopActivities(ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {//targetStack == null,  target == null,   targetOptions == null// .....    //遍历所有Displayfor (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {boolean resumedOnDisplay = false;final ActivityDisplay display = mActivityDisplays.get(displayNdx);//遍历所有ActivityStackfor (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {final ActivityStack stack = display.getChildAt(stackNdx);//每个ActivityStack中的顶部正在运行着的Activityfinal ActivityRecord topRunningActivity = stack.topRunningActivityLocked();//isFocusableAndVisible方法返回栈顶部Activity可见并且有焦点if (!stack.isFocusableAndVisible() || topRunningActivity == null) {continue;}if (stack == targetStack) {resumedOnDisplay |= result;continue;}//分屏主窗口的栈是否在Display顶部 并且 其栈顶部的Activity是否已经是RESUMED状态if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) {//直接执行栈转换动画stack.executeAppTransition(targetOptions);} else {//否则修改Activity的状态resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);}}if (!resumedOnDisplay) {....}}return result;}

此方法核心是调用makeActiveIfNeeded修改当前Activity的状态,

ActivityRecord.makeActiveIfNeeded:

boolean makeActiveIfNeeded(ActivityRecord activeActivity) {//修改Activity状态为resumeif (shouldResumeActivity(activeActivity)) {if (DEBUG_VISIBILITY) {Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);}return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,null /* options */);} else if (shouldPauseActivity(activeActivity)) {  //修改Activity状态为pause......}return false;}

仅关注resume流程

ActivityStack.resumeTopActivityUncheckedLocked:

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {  //传过来的prev和options都为空//当前是否正在resume中,防止重复调用if (mInResumeTopActivity) {// Don't even start recursing.return false;}boolean result = false;try {// Protect against recursion.mInResumeTopActivity = true;result = resumeTopActivityInnerLocked(prev, options);......return result;}

ActivityStack.resumeTopActivityInnerLocked:

 private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {...ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);final boolean hasRunningActivity = next != null;.....final ActivityDisplay display = getDisplay();if (mResumedActivity == next && next.isState(RESUMED状态)&& display.allResumedActivitiesComplete()) {// 如果当前分屏主窗口Activity已经是RESUMED状态,仅执行窗口动画executeAppTransition(options);if (DEBUG_STATES) Slog.d(TAG_STATES,"resumeTopActivityLocked: Top activity resumed " + next);return false;}//.....省略一些判断条件//当前要resume的Activity有可能正在stop或者sleep,所以需要从对应集合移除mStackSupervisor.mStoppingActivities.remove(next);mStackSupervisor.mGoingToSleepActivities.remove(next);next.sleeping = false;// 如果当前有正在pause的Activity,则跳过此次resumeif (!mRootActivityContainer.allPausedActivitiesComplete()) {if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,"resumeTopActivityLocked: Skip resume: some activity pausing.");//分屏主窗口Activity首次会走这里,因为要先等Launcher进入pause之后才能resumereturn false;}mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);//省略大段代码...........boolean anim = true;final DisplayContent dc = getDisplay().mDisplayContent;if (prev != null) {//这里prev是launcher,if (prev.finishing) {//如果launcher正在finishingif (mStackSupervisor.mNoAnimActivities.contains(prev)) {anim = false;//没有动画dc.prepareAppTransition(TRANSIT_NONE, false);} else {//有动画就开始准备窗口转换的动画,如果prev和next处于同一task,则准备Activity close动画,否则准备Task close动画dc.prepareAppTransition(prev.getTaskRecord() == next.getTaskRecord() ? TRANSIT_ACTIVITY_CLOSE: TRANSIT_TASK_CLOSE, false);}prev.setVisibility(false);} else {//如果launcher没有finishingif (mStackSupervisor.mNoAnimActivities.contains(next)) {anim = false;//没有动画dc.prepareAppTransition(TRANSIT_NONE, false);} else {//有动画就开始准备窗口转换的动画,如果prev和next处于同一task,则准备Activity 打开动画,否则准备Task 打开动画dc.prepareAppTransition(prev.getTaskRecord() == next.getTaskRecord() ? TRANSIT_ACTIVITY_OPEN: next.mLaunchTaskBehind ? TRANSIT_TASK_OPEN_BEHIND: TRANSIT_TASK_OPEN, false);}}} else {//这里prev为空的情况,if (mStackSupervisor.mNoAnimActivities.contains(next)) {anim = false//没有动画dc.prepareAppTransition(TRANSIT_NONE, false);} else {//直接准备Activity打开动画dc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false);}}if (anim) {next.applyOptionsLocked();} else {next.clearOptionsLocked();}mStackSupervisor.mNoAnimActivities.clear();//分屏主窗口Activity所在的进程是否已经创建if (next.attachedToProcess()) {final boolean lastActivityTranslucent = lastFocusedStack != null&& (lastFocusedStack.inMultiWindowMode()|| (lastFocusedStack.mLastPausedActivity != null&& !lastFocusedStack.mLastPausedActivity.fullscreen));if (!next.visible || next.stopped || lastActivityTranslucent) {//设置分屏主窗口为可见next.setVisibility(true);}......//设置分屏主窗口Activity状态为RESUMEDnext.setState(RESUMED, "resumeTopActivityInnerLocked");.....//这个方法里面又有一堆条件判断是否可见if (shouldBeVisible(next)) {// We have special rotation behavior when here is some active activity that// requests specific orientation or Keyguard is locked. Make sure all activity// visibilities are set correctly as well as the transition is updated if needed// to get the correct rotation behavior. Otherwise the following call to update// the orientation may cause incorrect configurations delivered to client as a// result of invisible window resize.// TODO: Remove this once visibilities are set correctly immediately when// starting an activity.notUpdated = !mRootActivityContainer.ensureVisibilityAndConfig(next, mDisplayId,true /* markFrozenIfConfigChanged */, false /* deferResume */);}......//后面就是执行Activity生命周期了.......//通过ResumeActivityItem到APP进程去执行Activity resume的流程transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(next.app.getReportedProcState(),getDisplay().mDisplayContent.isNextTransitionForward()));//执行Activity resumemService.getLifecycleManager().scheduleTransaction(transaction);} catch (Exception e) {......return true;}// From this point on, if something goes wrong there is no way// to recover the activity.try {//Activity完成resume的通知,包括修改各种状态等next.completeResumeLocked();} catch (Exception e) {return true;}} else {......}return true;}

这个方法实在太复杂了,省略了大段细节代码,需要关注的核心就是分屏主窗口Activity的状态被修改为了RESUME,然后到分屏应用进程去执行其Activity的生命周期。

总结下来分屏在AMS这边流程可以归纳为:设置分屏栈->计算分屏主副窗口的边界大小->将分屏应用放到前台来。

而真正窗口大小变化和动画会交给WMS来做。


http://chatgpt.dhexx.cn/article/W17dpp5v.shtml

相关文章

Android分屏显示(多窗口支持) 开发总结

最近公司有分屏显示需求,遇到几点问题 ,在此记录,以做备忘。 我所谓的分屏显示,是在同一个界面内,分屏显示两个app的界面 或者是 注意点1: 系统好像没有获取应用显示的左右或者上下位置的方法,…

android安卓手机分屏多窗口实现方法

效果图 frameborder"0" allowtransparency"true" scrolling"no" vspace"0" hspace"0" style"display: block; position: static; padding: 0px; margin: 0px; border-style: none; vertical-align: baseline; width: 3…

Android实现一键开启自由窗口、分屏、画中画模式——画中画模式

转载请注明出处:https://blog.csdn.net/sunmmer123 Android实现一键开启自由窗口、分屏、画中画模式系列 一键开启进入自由窗口模式一键开启进入分屏模式一键进入画中画模式 通过前俩篇博文,我们学习了一些多窗口模式中的自由窗口模式与分屏模式的知识 最…

Android多窗口模式(分屏模式)

Android N 支持多窗口模式,或者叫分屏模式,即在屏幕上可以同时显示多个窗口。 在手机模式下,两个应用可以并排或者上下同时显示,如图 1 所示,屏幕上半部分的窗口是系统的 CLOCK 应用,下半部分是系统设置功能…

git 命令怎么创建新分支?

问题 比如:我们要以 dev 分支创建一个 test-branch 分支。 解决 1、在本地创建一个 test-branch 分支,并切换到该分支。 git checkout -b test-branch执行完,可以使用下面命令查看是否创建了该本地分支 git branch -a2、把分支推到远程仓…

git命令之快速搭建远程仓库

首先使用系统管理员账号登录远程服务器,具体步骤如下所示: 1 安装git应用程序 sudo apt-get install git 2 创建git用户组和git用户,具体命令如下所示: groupadd git adduser git -g git 3 修改git用户默认shell为git-shell…

Git命令常用操作 代码拉取和提交

常规的git命令就是以下几个语句 项目首次拉取 git clone 项目地址url拉取并同步代码到本地 git pull将本地所有更改的文件添加到缓存区 git add .添加备注,方便之后查看历史提交记录 git commit -m "本次提交备注信息"提交到git仓库 git push

git命令之新建仓库

一. 配置git用户名 1)安装git,配置用户名和邮箱 git config --global user.name "你的用户名" git config --global user.email "你的注册邮箱" 2)使用git config --global --list查看配置 二. 工作区操作 1&#x…

常见Git命令使用

常见Git命令使用 1.初始化创库 git init 2.git status 这个命令顾名思义就是查看状态, 这个命令可以算是使用最频繁的一个命令了, 建议大家没事就输入下这个命令, 来查看你当前 git 仓库的一些状态。 3.git add 文件名 将文件添加到git仓库…

git命令拉取代码

流程 1在本地clone项目【保持与远程仓库一致】 此时已绑定远程仓库 git clone xxxx2.添加文件 3.放到暂存区 git add .4.提交到本地仓库 git commint -m "提示信息"5推送到远程仓库 git push origin master其他命令 分支命令 分支就是每个人开发 互不影响 之后…

Git 命令之stash

Git 命令之stash 前言1 stash1.1 描述1.2 应用场景命令使用 前言 使用 Git 作为代码版本管理,早已是现在开发工程师必备的技能。可大多数工程师还是只会最基本的保存、拉取、推送,遇到一些commit管理的问题就束手无策,或者用一些不优雅的方式…

git命令登录

1. 执行登陆用户名和密码命令 git config --global user.email "youexample.com" git config --global user.name "Your Name"2. 生成密钥对 ssh-keygen -t rsa -C "your_emailyouremail.com"3. 配置公钥私钥 然后将 .ssh/id_rsa.pub中的内容复…

git命令详解

一、简介 git作为应用广泛的一种分布式版本控制系统,其与svn比较最大的差别就是一个是分布式,一个是集中式,git在每个开发者的本地有一个完整的版本库,当在本地处理工作时,无需联网便可修改提交,当需要与其…

关于Git这一篇就够了

目录 前言 发展过程 集中式与分布式的区别 Debian/Linux安装Git 配置git环境:git config --global 创建本地空仓库:git init 新建文件添加到本地仓库:git add、git commit -m 改写提交:git commit --amend 查看历史提交日…

【信号与系统】—知识点:自由响应、强迫响应、暂态响应、稳态响应、零输入响应、零状态响应如何区分!

自由响应、强迫响应、暂态响应、稳态响应、零输入响应、零状态响应如何区分! 一、系统的响应划分 二、自由响应、强迫响应 自由响应和强迫响应是从数学上来分解的,没有什么物理意义,自由响应是齐次解,因为它的形式和激励无关&am…

一阶电路的零状态响应

零状态响应就是电路在零初始状态下(动态元件初始储能为零)由外施激励引起的响应。 RC电路的零状态响应 在t0时刻,开关S闭合,电路接入直流电压源US。根据KVL,有 uRuCUS (KVL ∑u0 指定回路的绕行方向是顺时针的,R、C的电压参考方向…

MATLAB:零状态响应(lsim(连续);filter(离散))、冲激响应(impulse或impz)和阶跃响应(step)、卷积(conv)

例1:求系统y(t)2y(t)100y(t)10x(t)的零状态响应,已知x(t)sin(2pt)u(t)。【连续时间系统零状态响应】 例2:求系统y(t)2y(t)100y(t)10x(t)的零状态响应,已知x(t)d(t)。(ts0;te5;dt0.01)【连续时间系统冲激响应…

连续系统的时域分析(一)LTI连续系统微分方程解法3——零状态响应的求解方法

(一)零状态响应的定义 零状态响应是系统在初始状态为零时,仅有输入信号 f ( t ) f(t) f(t)引起的响应。用 y z s ( t ) y_{zs}(t) yzs​(t)表示 (二)解题步骤 (1)当微分方程右端不含冲击函数 δ ( t ) \delta(t) δ…

《信号与系统》连续时间系统零状态响应的 MATLAB 实现

3.5.1 连续时间系统零状态响应的 MATLAB 实现 参考书籍 《信号与系统》 Matlab 库函数中的 **lsim()**能对微分方程描述的 LTI 连续时间系统的响应进行仿真。 lsim(b,a,x,t) 该调用格式中,a 和 b 是由描述系统的微分方程左边和右边系数构成的两个行向量&#xff…

Matlab计算信号和的卷积,求解微分方程的阶跃响应、冲激响应和零状态响应

参考资料: 连续时间系统的时域分析涉及到的Matlab函数: step:用于计算连续时间系统的单位阶跃响应。 impulse:用于计算连续时间系统的单位冲激响应。 lsim:用于计算连续系统在任意输入作用下的响应。 dsolve&…