Android 多窗口实现原理

参考链接:
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);
		}
	}
}
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值