文章目录
- 多窗口原理和框架介绍,多窗口框架的核心思想是分栈和设置栈边界
- 1.代码分布
- 多窗口的数据结构理解
- 1.栈
- 2.
- 3.
- 4.AMS和WMS数据结构
- 栈边界
- 进入/退出多窗口按以下逻辑框架顺序进行处理:
- 关键函数
- 分析整个分屏的流程
- 创建Divider
- 初始化SnapTarget
- 如何分栈和设置栈边界
- 分屏模式分栈
- 分屏模式设置栈边界
- Resize窗口
- 显示对应的Activity
参考链接:
1.Android 多窗口框架全解析 2.Android7.0多窗口实现原理(一)
3.Android7.0多窗口实现原理(二)
4.Android 多窗口实现
多窗口原理和框架介绍,多窗口框架的核心思想是分栈和设置栈边界
1.代码分布
多窗口主要涉及ActivityManagerService相关类、WindowManagerService相关类、SystemUI里面的核心类,代码如下:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
frameworks/base/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
frameworks/base/services/core/java/com/android/server/wm/TaskGroup.java
frameworks/base/services/core/java/com/android/server/wm/Task.java
frameworks/base/services/core/java/com/android/server/wm/TaskStack.java
frameworks/base/services/core/java/com/android/server/wm/TaskPositioner.java
frameworks/base/services/core/java/com/android/server/am/TaskPersister.java
frameworks/base/services/core/java/com/android/server/am/TaskRecord.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/
多窗口的数据结构理解
1.栈
Android原生多窗口是多Stack方案,即存在多个ActivityStack。ActivityStack是一个抽象的栈,每个栈都有自己的屏幕区域bound和id,Activity是以Task方式组织并放在某一个Stack中的。比如,Launcher、Recents属于id=HOME_STACK的栈中,普通的Activity所在的Stack的id是FULLSCREEN_WORKSPACE_STACK_ID,分屏模式下,上半部分窗口里面的Activity所处的栈ID是DOCKED_STACK_ID;
2.
每个Activity显示在所属ActivityStack的bound区域内,多个Activity显示在各自ActivityStack的bound区域内,这样就可以实现多窗口。多窗口不仅仅是控制Activity放入不同ActivityStack中,同时还要改变Activity的生命周期,即FocusActivity是resume状态,其他可见Activity是Pause状态,并不会进入Stop状态。
3.
整个系统中只会有一个FocusStack,一个FocusActivity。用户在哪个Activity中操作,FocusActivity便指向该Activity,FocusStack便指向FocusActivity所属的Stack。
4.AMS和WMS数据结构
AMS和WMS中对Stack分别用ActivityStack和TaskTack描述,通过StackId来映射。对Task分别用TaskRecord、Task描述,通过TaskId来映射。
栈边界
在多窗口框架中,通过设置Stack的边界(Bounds)来控制里面每个Task的大小,最终Task的大小决定了窗口的大小。栈边界通过Rect(left,top,right,bottom)来表示,存储了四个值,分别表示矩形的4条边离坐标轴的位置,最终显示在屏幕上窗口的大小是根据Stack边界的大小来决定的。
如图,为分屏模式下的Activity的状态。整个屏幕被分成了两个Stack,一个DockedStack,一个FullScreenStack。每个Stack里面有多个Task,每个Task里面又有多个Activity。当我们设置了Stack的大小之后,Stack里面的所有的Task的大小以及Task里面所有的Activity的窗口大小都确定了。假设屏幕的大小是1440x2560,整个屏幕的栈边界就是(0,0,1440,2560)。
本文主要讲解多窗口分屏模式的实现方式,对于分屏模式而言,当长按预览键的时候,屏幕会分成上下两个窗口,不同的窗口对应不同的Stack,上面的窗口此时对应的是Docked Stack,底部窗口是Home Stack(处于多任务界面)。如上图所示,手机被分成上下两块区域,也就是两个Stack,每个stack里面会包含很多的Task,而每个Task里面又包含了多个Activity,最终系统通过控制每个Stack的大小,来控制每个Task的大小,然后控制了Task里面的Activity的窗口的大小,所以最终控制了用户肉眼看到的每个小屏幕窗口大小。
进入/退出多窗口按以下逻辑框架顺序进行处理:
- step1.调整ActivityStack栈
- step2.调整Task栈
- step3.调整Activity堆栈,调整WMS中APPWindowToken堆栈
- step4.调用Activity生命周期函数
- step5.添加Window到WMS
- step6.过度动画及应用窗口显示
关键函数
1.移栈:
AMS.moveTaskToStack()
AMS.moveTaskToDockedStack()
AMS.moveTopActivityToPinnedStack()
2.分屏相互切换:
AMS.swapDockedAndFullscreenStack()
真正切换task(重点):
TaskRecord.reparent()//8.0新增分屏task移动都在此函数执行
3.调整栈大小:
AMS.resizeDockedStack()
AMS.resizePinnedStack()
ActivityStackSupervisor.resizeStackUncheckedLocked()
Activity生命周期:
ActivityStack.resumeTopActivityInnerLocked():
Activity启动:
ActivityStarter.startActivityUnchecked()
分析整个分屏的流程
创建Divider
/SystemUI/src/com/android/systemui/stackdivider/Divider.java
private void addDivider(Configuration configuration) {
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
mView.injectDependencies(mWindowManager, mDividerState, this);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable);
final int size = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? size : MATCH_PARENT;
final int height = landscape ? MATCH_PARENT : size;
mWindowManager.add(mView, width, height);
}
中间的白色小点是DividerHandleView自定义的xml控件
初始化SnapTarget
为了确定上下两个stack的大小,设计了SnapTarget的概念,每个SnapTarget相当于一块区域,中间的分割线可以停留在每个区域的底部。在开机过程中,系统会根据屏幕的分辨率来创建不同个数的SnapTarget.
frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
mTargets.clear();
int dividerMax = isHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
int navBarSize = isHorizontalDivision ? mInsets.bottom : mInsets.right;
int startPos = -mDividerSize;
if (dockedSide == DOCKED_RIGHT) {
startPos += mInsets.left;
}
mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
0.35f));
switch (mSnapMode) {
case SNAP_MODE_16_9:
addRatio16_9Targets(isHorizontalDivision, dividerMax);
break;
case SNAP_FIXED_RATIO:
addFixedDivisionTargets(isHorizontalDivision, dividerMax);
break;
case SNAP_ONLY_1_1:
addMiddleTarget(isHorizontalDivision);
break;
case SNAP_MODE_MINIMIZED:
addMinimizedTarget(isHorizontalDivision, dockedSide);
break;
}
mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
SnapTarget.FLAG_DISMISS_END, 0.35f));
}
简单介绍一下SnapTarget,看构造方法。
public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
this.position = position;
this.taskPosition = taskPosition;
this.flag = flag;
this.distanceMultiplier = distanceMultiplier;
}
position:离屏幕顶部的位置,最终决定了分割线停住的位置。
taskPostion: 和postion差不多,主要是用来计算每个Task边界的位置。
flag: 控制滑动到某个位置的时候是否退出分屏模式,比如我们将分割线滑动到靠近屏幕底部或者屏幕顶部的时候,会退出分屏。
distanceMultiplier:退出分屏模式的距离因子,值越大表示越不容易退出。假设总共高度是1000px,我们需要滑到900px的地方则退出分屏模式。如果distanceMultiplier是1,相当于没有起作用,还是900px退出。如果是0.5,那么1000-900/0.5=200,相当于我们滑动800的地方就会退出了。
如何分栈和设置栈边界
分屏模式分栈
step1. 当我们长按多任务按键之后,系统判断当前是否支持分屏模式,如果手机屏幕过小或者没有配置支持分屏,那么直接返回,否则触发分屏模式。
PhoneStatusBar.java
private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mRecents == null || !ActivityManager.supportsMultiWindow()
|| !getComponent(Divider.class).getView().getSnapAlgorithm()
.isSplitScreenFeasible()) {
return false;
}
toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
return true;
}
};
step2-3. 其他代码细节此处不做详表,在RecentsImpl里面,会涉及到我们上面提到的两步核心。分栈和设置栈边界。分栈是通过moveTaskToDockedStack来实现。将当前的Task移动到对应的Docked Stack里面。设置栈边界是通过EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)来实现。
RecentsImpl.java
public void dockTopTask(int topTaskId, int dragMode,
int stackCreateMode, Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
showRecents(
false /* triggeredFromAltTab */,
dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
false /* animate */,
true /* launchedWhileDockingTask*/,
false /* fromHome */,
DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
Step4-9 .通过SystemServiceProxy代理,直接调用到ActivityManagerService.java的moveTaskToDockedStack,首先,系统会调用mWindowManager.setDockedStackCreateState,为了方便后面在WindowManager里面计算stack的大小。接下来调用moveTaskToStackLocked将当前的Task移动到Docked Stack里面。
@Override
public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
Rect initialBounds, boolean moveHomeStackFront) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
final boolean moved = mStackSupervisor.moveTaskToStackLocked(
taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
animate, DEFER_RESUME);
if (moved) {
if (moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
return moved;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
mStackSupervisor.moveTaskToStackLocked
在ActivityStackSupervisor.java里面会调用WindowManager的moveTaskToStack
方法,然后通过TaskStack的addTask方法,最终将当前的Task添加进mStack变量里面。在ActivityStackSupervisor.java里面,如红线所示,如果当前的task在移动之前有焦点。那么就会将当前的task移动到栈的最前面,而且会重新更新所有的windows,这样当系统在后面重新请求绘制Window的时候,Window在Z轴上的位置是正确的。
ActivityStackSupervisor.java
ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
// omitted code
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable = false;
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
stack.addTask(task, toTop, reason);
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack by moving the stack to the front.
stack.moveToFrontAndResumeStateIfNeeded(
r, forceFocus || wasFocused || wasFront, wasResumed, reason);
return stack;
}
分栈的整个流程我们就介绍到这里了。具体的细节不妨碍我们了解整个架构,不在详细描述。接下来我们看是如何设置栈边界的。
分屏模式设置栈边界
step11-13
接下来我们看EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds)方法。EventBus是SystemUI里面用来发送消息的一个机制,通过反射来实现相关功能。使用方法大致如下:
- 首先在需要订阅的类里面通过EventBus.getDefault().register(this)注册监听,表示订阅了某一种消息。
- 然后定制public final void onBusEvent()方法。
- 最后调用send或者post方法,将消息发布出去,所有订阅了此消息的类都会收到此消息。
在DividerView里面,注册了EventBus事件。
DividerView.java
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
EventBus.getDefault().register(this);
mSurfaceFlingerOffsetMs = calculateAppSurfaceFlingerVsyncOffsetMs();
}
当我们执行了send之后,由于参数是DockedTopTaskEvent,那么会执行参数是DockedTopTaskEvent的onBusEvent方法。
DividerView
public final void onBusEvent(DockedTopTaskEvent event) {
if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
mState.growAfterRecentsDrawn = false;
mState.animateAfterRecentsDrawn = true;
startDragging(false /* animate */, false /* touching */);
}
updateDockSide();
int position = DockedDividerUtils.calculatePositionForBounds(event.initialRect,
mDockSide, mDividerSize);
mEntranceAnimationRunning = true;
// Insets might not have been fetched yet, so fetch manually if needed.
if (mStableInsets.isEmpty()) {
SystemServicesProxy.getInstance(mContext).getStableInsets(mStableInsets);
mSnapAlgorithm = null;
initializeSnapAlgorithm();
}
resizeStack(position, mSnapAlgorithm.getMiddleTarget().position,
mSnapAlgorithm.getMiddleTarget());
}
接下来根据事件的初始化边界position和middle target的位置来对栈进行resize操作。注意此时并没有完全确认上下屏的大小。
由于RecentsActivity.java也注册了订阅者。当我们调用了RecentsImpl.java里面的showRecents方法的时候,同时也会调用RecentsActivity.java里面的onBusEvent方法。
RecentsActivity.java
public final void onBusEvent(final DockedTopTaskEvent event) {
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
mRecentsView.invalidate();
}
当前View进行PreDraw的时候,就会调用回调onPreDraw()方法。
public boolean onPreDraw() {
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
// We post to make sure that this information is delivered after this traversals is
// finished.
mRecentsView.post(new Runnable() {
@Override
public void run() {
Recents.getSystemServices().endProlongedAnimations();
}
});
return true;
}
接下来会根据中间分割线的位置,以及最终分割停留的位置(middle snap target的位置),在stopDragging里面,不断的通过动画,最终将当前Task的边界的底部设置成middle snaptarget的postion.然后请求ActivityManagerService.java进行resize的动作。
DividerView.java
public final void onBusEvent(RecentsDrawnEvent drawnEvent) {
if (mState.animateAfterRecentsDrawn) {
mState.animateAfterRecentsDrawn = false;
updateDockSide();
mHandler.post(() -> {
// Delay switching resizing mode because this might cause jank in recents animation
// that's longer than this animation.
stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(),
mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
200 /* endDelay */);
});
}
if (mState.growAfterRecentsDrawn) {
mState.growAfterRecentsDrawn = false;
updateDockSide();
EventBus.getDefault().send(new RecentsGrowingEvent());
stopDragging(getCurrentPosition(), mSnapAlgorithm.getMiddleTarget(), 336,
Interpolators.FAST_OUT_SLOW_IN);
}
}
step 15.
ActivityManagerService.java里面会直接请求ActivityStackSupervisor.java 重新设置上下屏的边界。如下代码所示,
1.表示设置DockedStack的bounds;
2.表示获取下屏的Bounds;
3.表示根据第2步获取的bounds设置当前stack的bounds。
Resize窗口
当获得分屏的stack size后,如果当前的stack为分屏的stack,就调用resizeDockedStackLocked
函数来对分屏stack resize。
- 首先通过getStack函数从mActivityContainers列表中取出之前创建的DOCKED STACK,也就是分屏的stack,获取分屏stack中最顶端正在运行的ActivityRecord。
- 之后调用resizeStackUncheckedLocked函数进行对分屏stack进行resize。
ActivityStackSupervisor.java
void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
boolean preserveWindows, boolean deferResume) {
// 1.
resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
tempDockedTaskInsetBounds);
// 2 .
mWindowManager.getStackDockedModeBounds(
HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
// 3.
resizeStackLocked(i, tempRect, tempOtherTaskBounds,
tempOtherTaskInsetBounds, preserveWindows,
true /* allowResizeInDockedMode */, deferResume);
}
}
}
//...
}
在ActivityStackSupervisor.java里面,由于上下小屏的最小宽度以及屏幕高度和宽度等发生了变化,需要更新bounds,将DOCKED STACK中的所有task取出来, 如果task可以resize的话就更新Configuration,将新的Task bounds设置给对应task,将所有task进行冻结。( 故会对当前的Task的config进行重新配置)
void resizeStackUncheckedLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
Rect tempTaskInsetBounds) {
//...
for (int i = tasks.size() - 1; i >= 0; i--) {
final TaskRecord task = tasks.get(i);
if (task.isResizeable()) {
if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
// For freeform stack we don't adjust the size of the tasks to match that
// of the stack, but we do try to make sure the tasks are still contained
// with the bounds of the stack.
tempRect2.set(task.mBounds);
fitWithinBounds(tempRect2, bounds);
task.updateOverrideConfiguration(tempRect2);
} else {
task.updateOverrideConfiguration(taskBounds, insetBounds);
}
}
//...
stack.mFullscreen = mWindowManager.resizeStack(stack.mStackId, bounds, mTmpConfigs,
mTmpBounds, mTmpInsetBounds);
stack.setBounds(bounds);
}
task.updateOverrideConfiguration
mWindowManager.resizeStack
最后,调用WMS的resizeStack函数最终对stack里面所有的task重新计算一下尺寸,将最终获取到的分屏size bounds设置给DOCKED STACK。
step16-22.
在WindowManagerService.java里面设置当前Stack的bounds。
在WindowManagerService的resizeStack函数中,根据 stackId从mStackIdToStack中获取到对应的TaskStack,然后调用TaskStack的setBounds来设置边界。当设置边界成功后,并且该stack可以被看到就进行layout。最后将stack是否全屏返回。
@WindowManagerService.java
public void resizeTask(int taskId, Rect bounds, Configuration configuration,
boolean relayout, boolean forced) {
synchronized (mWindowMap) {
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
throw new IllegalArgumentException("resizeTask: taskId " + taskId
+ " not found.");
}
if (task.resizeLocked(bounds, configuration, forced) && relayout) {
task.getDisplayContent().layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
}
}
}
可以看到当前设置的是TaskStack的边界。
在TaskStack的setBounds的函数中,首先调用setBounds函数来为stack设置边界,最后遍历mTasks列表中的task对象,并且取出对应的config,对每一个task进行设置边界。
在setBounds里面会更新当前TaskStack的bounds,接下来会更新TaskStack里面所有的Task的边界。
boolean setBounds(
Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
SparseArray<Rect> taskTempInsetBounds) {
setBounds(stackBounds);
// Update bounds of containing tasks.
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = mTasks.get(taskNdx);
Configuration config = configs.get(task.mTaskId);
if (config != null) {
Rect bounds = taskBounds.get(task.mTaskId);
if (task.isTwoFingerScrollMode()) {
// This is a non-resizeable task that's docked (or side-by-side to the docked
// stack). It might have been scrolled previously, and after the stack resizing,
// it might no longer fully cover the stack area.
// Save the old bounds and re-apply the scroll. This adjusts the bounds to
// fit the new stack bounds.
task.resizeLocked(bounds, config, false /* forced */);
task.getBounds(mTmpRect);
task.scrollLocked(mTmpRect);
} else {
task.resizeLocked(bounds, config, false /* forced */);
task.setTempInsetBounds(
taskTempInsetBounds != null ? taskTempInsetBounds.get(task.mTaskId)
: null);
}
} else {
Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
}
}
return true;
}
在task.resizeLocked里面,会最终设置Task的mBounds变量。也就是我们本文介绍的Task边界。至此,Task的边界bounds已经设置完毕。
显示对应的Activity
将分屏stack的size重新计算,并且将stack中的所有task重新计算边界后。重新回到resizeDockedStackLocked函数中继续往下执行第九步,getStackDockedModeBounds通过分屏stack的边界,获取到home stack的边界大小。
在WMS中根据stackId获取到mStackIdToStack列表中保存的TaskStack对象,调用TaskStack的getStackDockedModeBoundsLocked函数来获得home stack边界。
在getStackDockedModeBoundsLocked函数中根据DOCKED_STACK_ID获取到mStackIdToStack保存的对应dockedStack对象。
/**
* Determines the stack and task bounds of the other stack when in docked mode. The current task
* bounds is passed in but depending on the stack, the task and stack must match. Only in
* minimized mode with resizable launcher, the other stack ignores calculating the stack bounds
* and uses the task bounds passed in as the stack and task bounds, otherwise the stack bounds
* is calculated and is also used for its task bounds.
* If any of the out bounds are empty, it represents default bounds
*
* @param currentTempTaskBounds the current task bounds of the other stack
* @param outStackBounds the calculated stack bounds of the other stack
* @param outTempTaskBounds the calculated task bounds of the other stack
*/
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;
}
final int dockedSide = getDockSide(parentConfig, dockedBounds);
// When the home stack is resizable, should always have the same stack and task bounds
if (isActivityTypeHome()) {
final Task homeTask = findHomeTask();
if (homeTask != null && homeTask.isResizeable()) {
// Calculate the home stack bounds when in docked mode and the home stack is
// resizeable.
getDisplayContent().mDividerControllerLocked
.getHomeStackBoundsInDockedMode(parentConfig,
dockedSide, outStackBounds);
} else {
// Home stack isn't resizeable, so don't specify stack bounds.
outStackBounds.setEmpty();
}
outTempTaskBounds.set(outStackBounds);
return;
}
// When minimized state, the stack bounds for all non-home and docked stack bounds should
// match the passed task bounds
if (isMinimizedDockAndHomeStackResizable() && currentTempTaskBounds != null) {
outStackBounds.set(currentTempTaskBounds);
return;
}
if (dockedSide == DOCKED_INVALID) {
// Not sure how you got here...Only thing we can do is return current bounds.
Slog.e(TAG_WM, "Failed to get valid docked side for docked stack");
outStackBounds.set(getRawBounds());
return;
}
final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
getStackDockedModeBounds(parentConfig,
false /* primary */, outStackBounds, dockedBounds,
mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
}
在获取到分屏stack后,如果不忽视stack的可视性,并且分屏stack不可见,就将设备的尺寸返回给home stack,全屏显示。否则获取分屏在那一侧dockedSide。将Home stack所在设备的屏幕尺寸保存在mTmpRect中,将dockedStack的矩形尺寸保存在mTmpRect2中,获取此时分屏在屏幕的位置dockedOnTopOrLeft。根据getStackDockedModeBounds函数获得分屏区之外HOME stack的矩形尺寸。
/**
* Outputs the bounds a stack should be given the presence of a docked stack on the display.
* @param parentConfig The parent configuration.
* @param primary {@code true} if getting the primary stack bounds.
* @param outBounds Output bounds that should be used for the stack.
* @param dockedBounds Bounds of the docked stack.
* @param dockDividerWidth We need to know the width of the divider make to the output bounds
* close to the side of the dock.
* @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
*/
private void getStackDockedModeBounds(Configuration parentConfig, boolean primary,
Rect outBounds, Rect dockedBounds, int dockDividerWidth,
boolean dockOnTopOrLeft) {
final Rect displayRect = parentConfig.windowConfiguration.getBounds();
final boolean splitHorizontally = displayRect.width() > displayRect.height();
outBounds.set(displayRect);
if (primary) {
if (mWmService.mDockedStackCreateBounds != null) {
outBounds.set(mWmService.mDockedStackCreateBounds);
return;
}
// The initial bounds of the docked stack when it is created about half the screen space
// and its bounds can be adjusted after that. The bounds of all other stacks are
// adjusted to occupy whatever screen space the docked stack isn't occupying.
final DisplayCutout displayCutout = mDisplayContent.getDisplayInfo().displayCutout;
mDisplayContent.getDisplayPolicy().getStableInsetsLw(
parentConfig.windowConfiguration.getRotation(),
displayRect.width(), displayRect.height(), displayCutout, mTmpRect2);
final int position = new DividerSnapAlgorithm(mWmService.mContext.getResources(),
displayRect.width(),
displayRect.height(),
dockDividerWidth,
parentConfig.orientation == ORIENTATION_PORTRAIT,
mTmpRect2).getMiddleTarget().position;
if (dockOnTopOrLeft) {
if (splitHorizontally) {
outBounds.right = position;
} else {
outBounds.bottom = position;
}
} else {
if (splitHorizontally) {
outBounds.left = position + dockDividerWidth;
} else {
outBounds.top = position + dockDividerWidth;
}
}
return;
}
// Other stacks occupy whatever space is left by the docked stack.
if (!dockOnTopOrLeft) {
if (splitHorizontally) {
outBounds.right = dockedBounds.left - dockDividerWidth;
} else {
outBounds.bottom = dockedBounds.top - dockDividerWidth;
}
} else {
if (splitHorizontally) {
outBounds.left = dockedBounds.right + dockDividerWidth;
} else {
outBounds.top = dockedBounds.bottom + dockDividerWidth;
}
}
DockedDividerUtils.sanitizeStackBounds(outBounds, !dockOnTopOrLeft);
}
由于stackId为为HOME_STACK_ID,dockedStack为false最后根据分屏是在左侧还是右侧,来重新计算,上,下,左,右,边界位置。如果分屏stack在屏幕左侧占屏幕一半,那么Home stack就在屏幕右侧占屏幕一半。
获取到分屏后Home Stack的尺寸后,回到第12步,遍历系统中的所有stack,如果该stack可以被分屏stack resize,并且该stack存在,就调用resizeStackLocked函数。
for(int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++){
}
从mActivityContainers中获取Home Stack,在resizeStackLocked函数中调用resizeStackUncheckedLocked函数,然后调用updateBoundsAllowed函数来判断是否需要更新边界,由于在开始分屏时调用ActivityStackSupervisor的startActivityFromRecentsInner函数中,将Home stack的边界延迟更新,设置mUpdateBoundsDeferred为true,所以在调用updateBoundsAllowed时就会将Home Stack边界,记录在mDeferredBounds中。
boolean updateBoundsAllowed(Rect bounds) {
if (!mUpdateBoundsDeferred) {
return true;
}
if (bounds != null) {
mDeferredBounds.set(bounds);
} else {
mDeferredBounds.setEmpty();
}
mUpdateBoundsDeferredCalled = true;
return false;
}
对分屏的stack size获取完之后,就可以启动Activity了。在ActivityStackSupervisor的startActivityFromRecentsInner函数的最后调用AMS的startActivityInPackage函数进行启动Activity。
当分屏的ActivityTransition完成后,就会调用ActivityStackSupervisor的notifyAppTransitionDone函数,这时就会继续更新HOME_STACK的边界
根据stackid获取Home stack,进行更新Home stack的边界。
/frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java
void continueUpdateBounds(int activityType) {
final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
stack.continueUpdateBounds();
}
}
获取Home stack中的mDeferredBounds,来对home stack进行更新。
base/services/core/java/com/android/server/wm/ActivityStack.java
/**
* Continues updating bounds after updates have been deferred. If there was a resize attempt
* between {@link #deferUpdateBounds()} and {@link #continueUpdateBounds()}, the stack will
* be resized to that bounds.
*/
void continueUpdateBounds() {
if (mUpdateBoundsDeferred) {
mUpdateBoundsDeferred = false;
if (mUpdateBoundsDeferredCalled) {
setTaskBounds(mDeferredBounds);
setBounds(mDeferredBounds);
}
if (mUpdateDisplayedBoundsDeferredCalled) {
setTaskDisplayedBounds(mDeferredDisplayedBounds);
}
}
}