Android 7.1.2(Android N) Multi-window-mode--多窗口加载显示流程、退出流程
(一)Multi-window-mode加载显示
首先贴一张总体流程图:
start-multi-window-mode.png(1)RecentsButton(虚拟键)触发事件
长按RecentsButton(虚拟键)会触发KeyButtonView.java的onTouchEvent()方法
按下(ACTION_DOWN)后开始计时,如果一段时间ViewConfiguration.getLongPressTimeout()后,没有释放(ACTION_UP)
说明用户想长按,于是我们的postDelayed扔出了一个Runnable来进行长按处理。如果在ViewConfiguration.getLongPressTimeout()之内,用户释放(ACTION_UP)了,那就是个短按事件了,就会进入Recents Task 加载显示流程。
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
if (isPressed()) {
// Log.d("KeyButtonView", "longpressed: " + this);
if (isLongClickable()) {
// Just an old-fashioned ImageView
performLongClick();
mLongClicked = true;
}
......
};
public boolean onTouchEvent(MotionEvent ev) {
......
switch (action) {
case MotionEvent.ACTION_DOWN:
......
//postDelayed长按事件
postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
break;
......
case MotionEvent.ACTION_UP:
......
}
进入performLongClick()->performLongClickInternal()方法
/frameworks/base/core/java/android/view/View.java
private boolean performLongClickInternal(float x, float y) {
......
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
......
}
SystemUI启动的SystemBars的时候会注册RecentsButton长按事件(由于是分析多窗口显示加载流程,这里暂且不分析SystemBars启动流程),recentsButton.setOnLongClickListener(mRecentsLongClickListener)
最终会在PhoneStatusBar.java 里面触发分屏toggleSplitScreenMode()
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
//recentsButton长按事件具体实现
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;
}
};
private void prepareNavigationBarView() {
......
ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
......
recentsButton.setOnLongClickListener(mRecentsLongClickListener);
......
}
这里我们看到,长按RecentsButton,代码逻辑为:
mRecents为空
不支持分屏
这里 1、upportsMultiWindow()方法判断了一个系统属性config_supportsMultiWindow为真 以及非低内存版本,则认为系统可以支持分屏 2、isSplitScreenFeasible( )方法判断当前分屏的大小,是否是满足系统要求的最小的分屏像素值。
/frameworks/base/core/java/android/app/ActivityManager.java
/**
* Returns true if the system supports at least one form of multi-window.
* E.g. freeform, split-screen, picture-in-picture.
* @hide
*/
static public boolean supportsMultiWindow() {
return !isLowRamDeviceStatic()
&& Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_supportsMultiWindow);
}
//isSplitScreenFeasible 判断当前分屏的大小,是否是满足系统要求的最小的分屏像素值。
/frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
//其中mMinimalSizeResizableTask 值为 <dimen name="default_minimal_size_resizable_task">220dp</dimen>
mMinimalSizeResizableTask = res.getDimensionPixelSize(
com.android.internal.R.dimen.default_minimal_size_resizable_task);
/**
* @return whether it's feasible to enable split screen in the current configuration, i.e. when
* snapping in the middle both tasks are larger than the minimal task size.
*/
public boolean isSplitScreenFeasible() {
int statusBarSize = mInsets.top;
int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
int size = mIsHorizontalDivision
? mDisplayHeight
: mDisplayWidth;
int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
return availableSpace / 2 >= mMinimalSizeResizableTask;
}
来到分屏的代码位置,这里有一个判断
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@Override
protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
EventBus.getDefault().send(new UndockingTaskEvent());
if (metricsUndockAction != -1) {
MetricsLogger.action(mContext, metricsUndockAction);
}
}
}
dockSide == WindowManager.DOCKED_INVALID 此状态表示当前没有处在分屏模式下,因此我们需要进入分屏
我们看下这里的WindowManagerProxy.getInstance().getDockSide()如何处理的
这里可以看到,来到了WMS(WindowManagerServer.java)位置,看下getDockedStackSide方法
/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
public int getDockSide() {
try {
return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
} catch (RemoteException e) {
Log.w(TAG, "Failed to get dock side: " + e);
}
return DOCKED_INVALID;
}
//这里如何判断的呢?找下当前的默认显示屏幕,然后判断下DockStack是否存在,如果存在,则在多窗口模式,如果不存在,则当前不是
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public int getDockedStackSide() {
synchronized (mWindowMap) {
final TaskStack dockedStack = getDefaultDisplayContentLocked()
.getDockedStackVisibleForUserLocked();
return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
}
}
我们这里在启动分屏,所以此时不在分屏模式模式,于是乎,我们来到代码:千里之行,启程。
/frameworks/base/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
public interface RecentsComponent {
......
/**
* Docks the top-most task and opens recents.
*/
boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction);
......
}
进入Recents.java的dockTopTask()方法
/frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@Override
public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
......
if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
......
if (sSystemServicesProxy.isSystemUser(currentUser)) {
//代码走到mImpl.dockTopTask,我们直接过来(mImpl==RecentsImpl.java)
mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
}
......
}
/frameworks/base/packages/SystemUI/src/com/android/systemui/recents/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);
}
}
我们看到了代码走入了moveTaskToDockedStack,这里继续跟进,我们看到了:
/frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
/** Docks an already resumed task to the side of the screen. */
public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
false /* animate */, initialBounds, true /* moveHomeStackFront */ );
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}
这里mIam就是ActivityManagerService的代理端。此时,此方法moveTaskToDockedStack则会通过binder,进入到ActivityManagerService的对应方法里面。
(2)AMS 与 WMS 交互
image.png我们来看下ActivityManagerService.java里面moveTaskToDockedStack方法的注释:
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
/**
* Moves the input task to the docked stack.
*
* @param taskId Id of task to move.
* @param createMode The mode the docked stack should be created in if it doesn't exist
* already. See
* {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
* and
* {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
* @param toTop If the task and stack should be moved to the top.
* @param animate Whether we should play an animation for the moving the task
* @param initialBounds If the docked stack gets created, it will use these bounds for the
* docked stack. Pass {@code null} to use default bounds.
*/
@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);
}
}
}
参数为:
需要移动到docked stack的task id
createMode 创建横屏的还是竖屏的分屏
toTop 是否将这个task 和stack移动到最上面
animate 是否需要一个动画
initialBounds 初始化docked stack的边界值
我们看下这里的实际传递的参数:
taskId 这个不用管,只需要知道当前正在运行的TASK的id值即可。
dragMode = NavigationBarGestureHelper.DRAG_MODE_NONE
stackCreateMode=ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
initialBounds = 屏幕大小信息,这里为 0 0 720 1280
moveHomeStackFront = true
animate=false
onTop = true
/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
String reason, boolean animate, boolean deferResume) {
......
//停止surface更新,我们需要更新数据,随后使用continueSurfaceLayout继续。我们可以理解成一个锁,锁住画布。
mWindowManager.deferSurfaceLayout();
final int preferredLaunchStackId = stackId;
boolean kept = true;
try {
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
stackId = stack.mStackId;
if (!animate) {
stack.mNoAnimActivities.add(topActivity);
}
// We might trigger a configuration change. Save the current task bounds for freezing.
mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
// Make sure the task has the appropriate bounds/size for the stack it is in.
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
Rect bounds = task.getLaunchBounds();
if (bounds == null) {
stack.layoutTaskInStack(task, null);
bounds = task.mBounds;
}
kept = resizeTaskLocked(task, bounds, RESIZE_MODE_FORCED, !mightReplaceWindow,
deferResume);
} else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
}
} finally {
mWindowManager.continueSurfaceLayout();
}
if (mightReplaceWindow) {
// If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
// window), we need to clear the replace window settings. Otherwise, we schedule a
// timeout to remove the old window if the replacing window is not coming in time.
mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken, !kept);
}
if (!deferResume) {
// The task might have already been running and its visibility needs to be synchronized with
// the visibility of the stack / windows.
ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
resumeFocusedStackTopActivityLocked();
}
handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);
return (preferredLaunchStackId == stackId);
}
来到moveTaskToStackUncheckedLocked方法处.
移动特定的任务到传入的stack id(我们传入的为DOCKED_STACK_ID,移动的是当前最上面的那个TASK)
task 需要移入的task
stackId 移入的stackid (DOCKED_STACK_ID)
toTop = true
forceFocus ,默认取得反值
forceFocus =false(不需要强制Focus)
reason 就是个注释,我们不管
返回我们最终移入 的stack信息
/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
/**
* Moves the specified task record to the input stack id.
* WARNING: This method performs an unchecked/raw move of the task and
* can leave the system in an unstable state if used incorrectly.
* Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
* @param task Task to move.
* @param stackId Id of stack to move task to.
* @param toTop True if the task should be placed at the top of the stack.
* @param forceFocus if focus should be moved to the new stack
* @param reason Reason the task is been moved.
* @return The stack the task was moved to.
*/
ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
......
// Temporarily disable resizeablility of task we are moving. We don't want it to be resized
// if a docked stack is created below which will lead to the stack we are moving from and
// its resizeable tasks being resized.
task.mTemporarilyUnresizable = true;
//获取这个栈,如果需要创建,创建它
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable = false;
//移动task到对应的stack上面
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
//然后我们当前的stack,存储下task
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;
}
//getStack()法
ActivityStack getStack(int stackId, boolean createStaticStackIfNeeded, boolean createOnTop) {
ActivityContainer activityContainer = mActivityContainers.get(stackId);
if (activityContainer != null) {
return activityContainer.mStack;
}
if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
return null;
}
return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
}
ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
//这里我们看到的ActivityDisplay 为获取对应displayId的一个实例,所以我们系统是支持多种显示设备的。
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
return null;
}
//创建一个ActivityContainer(stackId),用来实现stack栈信息,然后存储下来。
ActivityContainer activityContainer = new ActivityContainer(stackId);
mActivityContainers.put(stackId, activityContainer);
activityContainer.attachToDisplayLocked(activityDisplay, onTop);
return activityContainer.mStack;
}
void attachToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
+ " to display=" + activityDisplay + " onTop=" + onTop);
mActivityDisplay = activityDisplay;
//这里便是将这个stack挂在对应显示屏的列表上面(一般我们默认显示屏是手机)
mStack.attachDisplay(activityDisplay, onTop);
activityDisplay.attachActivities(mStack, onTop);
}
/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
我们看到了attachDisplay方法
void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
mDisplayId = activityDisplay.mDisplayId;
mStacks = activityDisplay.mStacks;
mBounds = mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
mFullscreen = mBounds == null;
if (mTaskPositioner != null) {
mTaskPositioner.setDisplay(activityDisplay.mDisplay);
mTaskPositioner.configure(mBounds);
}
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
mStackSupervisor.resizeDockedStackLocked(
mBounds, null, null, null, null, PRESERVE_WINDOWS);
}
}
关键方法attachStack,我们跟入看下:(首先看注释)
创建一个taskstack放置在对应的显示容器内
stackId ==栈Id,我们这里认为为DOCKED_STACK_ID
displayId =我们认为为默认屏,手机即可
onTop = true
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
/**
* Create a new TaskStack and place it on a DisplayContent.
* @param stackId The unique identifier of the new stack.
* @param displayId The unique identifier of the DisplayContent.
* @param onTop If true the stack will be place at the top of the display,
* else at the bottom
* @return The initial bounds the stack was created with. null means fullscreen.
*/
public Rect attachStack(int stackId, int displayId, boolean onTop) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
final DisplayContent displayContent = mDisplayContents.get(displayId);
boolean attachedToDisplay = false;
if (displayContent != null) {
TaskStack stack = mStackIdToStack.get(stackId);
if (stack == null) {
if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);
stack = displayContent.getStackById(stackId);
if (stack != null) {
// It's already attached to the display. Detach and re-attach
// because onTop might change, and be sure to clear mDeferDetach!
displayContent.detachStack(stack);
stack.mDeferDetach = false;
attachedToDisplay = true;
} else {
//创建了TaskStack
stack = new TaskStack(this, stackId);
}
//我们创建了TaskStack,于是系统通知systemui,显示divider线。
mStackIdToStack.put(stackId, stack);
if (stackId == DOCKED_STACK_ID) {
getDefaultDisplayContentLocked().mDividerControllerLocked
.notifyDockedStackExistsChanged(true);
}
}
if (!attachedToDisplay) {
//这里又出现一个方法,stack.attachDisplayContent(displayContent);我们仔细看下它
stack.attachDisplayContent(displayContent);
}
displayContent.attachStack(stack, onTop);
if (stack.getRawFullscreen()) {
return null;
}
Rect bounds = new Rect();
stack.getRawBounds(bounds);
return bounds;
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
return null;
}
/frameworks/base/services/core/java/com/android/server/wm/TaskStack.java
这里直接有值了,我们直接赋值返回了,于是我们说下这个值从哪赋值的mDockedStackCreateBounds
我们之前看到的,moveTaskToDockedStack --> 方法里面有个 mWindowManager.setDockedStackCreateState(createMode, initialBounds); 这里给了赋值。(需要看的可以向上重新回头阅读下这个信息)
void attachDisplayContent(DisplayContent displayContent) {
...... final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
== DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(),
dockedOnTopOrLeft);
}
updateDisplayInfo(bounds);
}
回到ActivityStack.java,attachDisplay方法,看到如下代码:
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
mStackSupervisor.resizeDockedStackLocked(
mBounds, null, null, null, null, PRESERVE_WINDOWS);
}
我们需要调整task的大小信息。
/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
boolean preserveWindows, boolean deferResume) {
......
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
mWindowManager.deferSurfaceLayout();
try {
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
mAllowDockedStackResize = false;
ActivityRecord r = stack.topRunningActivityLocked();
resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
tempDockedTaskInsetBounds);
......
if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
......
} else {
// Docked stacks occupy a dedicated region on screen so the size of all other
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
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) {
resizeStackLocked(i, tempRect, tempOtherTaskBounds,
tempOtherTaskInsetBounds, preserveWindows,
true /* allowResizeInDockedMode */, deferResume);
}
}
}
if (!deferResume) {
stack.ensureVisibleActivitiesConfigurationLocked(r, preserveWindows);
}
} finally {
mAllowDockedStackResize = true;
mWindowManager.continueSurfaceLayout();
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
......
}
-->deferSurfaceLayout() [WMS]
resizeDockedStackLocked() [ActivityStackSupervisor.java]
---->resizeStackUncheckedLocked() [ActivityStackSupervisor.java]
---->resizeStackLocked() [ActivityStackSupervisor.java]
-->continueSurfaceLayout() [WMS]
-->continueLayout) [WindowSurfacePlacer.java]
-->performSurfacePlacement() [WindowSurfacePlacer.java]
-->performSurfacePlacementLoop() [WindowSurfacePlacer.java]
-->performSurfacePlacementInner() [WindowSurfacePlacer.java]
-->applySurfaceChangesTransaction() [WindowSurfacePlacer.java]
-->performLayoutLockedInner() [WindowSurfacePlacer.java]
-->beginLayoutLw() && layoutWindowLw() [PhoneWindowManager.java]
-->computeFrameLw() [WindowState.java]
我们这里不再深入细化,因为这里逻辑太多,以后再做研究,我们当前需要了解的是:
系统这个时候,重新将所有的task大小计算,我们一般应用所在的FULL_SCREEN_STACK 会重新调整,然后给当前app通知进入分屏。
为什么讲这个呢?因为这里是系统向activity发出的回调,告知系统进入分屏模式,需要activity作出响应的地方。
image.png系统在此时发送了REPORT_MULTI_WINDOW_MODE_CHANGED_MSG消息出去。最终会到activity的onMultiWindowModeChanged 生命周期中。
Task和Stack
Android系统中的每一个Activity都位于一个Task中。一个Task可以包含多个Activity,同一个Activity也可能有多个实例。 在AndroidManifest.xml中,我们可以通过android:launchMode来控制Activity在Task中的实例。
另外,在startActivity的时候,我们也可以通过setFlag 来控制启动的Activity在Task中的实例。
Task管理的意义还在于近期任务列表以及Back栈。 当你通过多任务键(有些设备上是长按Home键,有些设备上是专门提供的多任务键)调出多任务时,其实就是从ActivityManagerService获取了最近启动的Task列表。
Back栈管理了当你在Activity上点击Back键,当前Activity销毁后应该跳转到哪一个Activity的逻辑。关于Task和Back栈,请参见这里:Tasks and Back Stack。
其实在ActivityManagerService与WindowManagerService内部管理中,在Task之外,还有一层容器,这个容器应用开发者和用户可能都不会感觉到或者用到,但它却非常重要,那就是Stack。 下文中,我们将看到,Android系统中的多窗口管理,就是建立在Stack的数据结构上的。 一个Stack中包含了多个Task,一个Task中包含了多个Activity(Window),下图描述了它们的关系:
image.png另外还有一点需要注意的是,ActivityManagerService和WindowManagerService中的Task和Stack结构是一一对应的,对应关系对于如下:
ActivityStack <–> TaskStack
TaskRecord <–> Task
即,ActivityManagerService中的每一个ActivityStack或者TaskRecord在WindowManagerService中都有对应的TaskStack和Task,这两类对象都有唯一的id(id是int类型),它们通过id进行关联。
总结:
如此一来,我们就创建出来DOCKED_STACK_ID的一个栈了,其中stack是维护task任务的,task是维护activity的。它们就是如此的关系。然后我们系统将创建好的stack关联到WMS,调整task的大小,然后通知当前的activity,我们当前进入分屏模式下了,你要在你的onMultiWindowModeChanged 里面做出响应。(看到了吗?我们分屏在activity的一个生命周期方法,在此处出现了)
moveTaskToStackUncheckedLocked 里面主要做了几件事情
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop); 获取DOCK_STACK,如果没有,就创建它
task.mTemporarilyUnresizable = false;
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop); 移动当前的task进入DOCK_STACK里面,更新状态,传递divider显示与否的消息到systemui,传递给activity onMultiWindowModeChanged,来告知消息。
stack.addTask(task, toTop, reason); 加入当前的AMS的管理里面即可。
后面触发了mWindowPlacerLocked.performSurfacePlacement();方法,引发绘制动作,我们的分屏启动完成了。
(3)Divider位置
我们再来看一个问题,就是我们的分屏,会在屏幕上画出一个分割线,这个线的位置如何定义出来的呢?
我们回到DividerWindowManager.java ,我们之前讲过,我们的分割线是在分屏开启后进行显示,加入到WMS里面去,我们可以看到一个关键信息
/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
public void add(View view, int width, int height) {
Log.i(TAG, "zhoujinjian DividerWindowManager add()");
mLp = new WindowManager.LayoutParams(
width, height, TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mLp.setTitle(WINDOW_TITLE);
mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mWindowManager.addView(view, mLp);
mView = view;
}
这里我们看到 TYPE_DOCK_DIVIDER,是这个View的类型,非常特殊。
我们搜索这个关键字,可以看到很多内容,我们简单说下里面一些:
WindowManagerService.java 里 addWindow,
if (type == TYPE_DOCK_DIVIDER) {
getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
}
这里为如果当前View的类型为TYPE_DOCK_DIVIDER 我们要加入到DockedDividerController里面,按照上一节的说法,这个DockedDividerController会在系统的Vsync里面,实时触发,这里则会判断是否有divider之类的状态。
/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
PhoneWindowManager.java 里面的 layoutWindowLw 方法:
else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (attrs.type == TYPE_STATUS_BAR
|| attrs.type == TYPE_TOAST
|| attrs.type == TYPE_DOCK_DIVIDER
|| attrs.type == TYPE_VOICE_INTERACTION_STARTING
|| (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
// Asking for layout as if the nav bar is hidden, lets the
// application extend into the unrestricted screen area. We
// only do this for application windows (or toasts) to ensure no window that
// can be above the nav bar can do this.
// XXX This assumes that an app asking for this will also
// ask for layout in only content. We can't currently figure out
// what the screen would be if only laying out to hide the nav bar.
pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
+ mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
+ mUnrestrictedScreenHeight;
}
给TYPE_DOCK_DIVIDER 赋值绘制区域,系统边界值的信息。
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
我们再看一个类WindowState.java,里面的关键方法computeFrameLw
有段内容:
else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
mContentFrame.set(mFrame);
if (!mFrame.equals(mLastFrame)) {
mMovedByResize = true;
}
}
我们打个断点在这里:
EM截图_2017121910554.png
我们惊奇的发现,我们的栈里面有performSurfacePlacementLoop,还有moveTaskToStackLocked-->mWindowManager.continueSurfaceLayout(); 这里触发了启动绘制。看到这条线路,我们可以找到整个窗体的计算过程路径。
这里我们关心的是这个分割线的位置:(这里mFrame便是计算好的位置信息了,我们当前值为 Rect(0, 568 - 720, 664),高96,看这里是不是在屏幕中间)
/frameworks/base/services/core/java/com/android/server/wm/DockedStackDividerController.java
void positionDockedStackedDivider(Rect frame) {
TaskStack stack = mDisplayContent.getDockedStackLocked();
if (stack == null) {
// Unfortunately we might end up with still having a divider, even though the underlying
// stack was already removed. This is because we are on AM thread and the removal of the
// divider was deferred to WM thread and hasn't happened yet. In that case let's just
// keep putting it in the same place it was before the stack was removed to have
// continuity and prevent it from jumping to the center. It will get hidden soon.
frame.set(mLastRect);
return;
} else {
stack.getDimBounds(mTmpRect);
}
int side = stack.getDockSide();
switch (side) {
case DOCKED_LEFT:
frame.set(mTmpRect.right - mDividerInsets, frame.top,
mTmpRect.right + frame.width() - mDividerInsets, frame.bottom);
break;
case DOCKED_TOP:
frame.set(frame.left, mTmpRect.bottom - mDividerInsets,
mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets);
break;
case DOCKED_RIGHT:
frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top,
mTmpRect.left + mDividerInsets, frame.bottom);
break;
case DOCKED_BOTTOM:
frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets,
frame.right, mTmpRect.top + mDividerInsets);
break;
}
mLastRect.set(frame);
}
依据我们的DOCKED_STACK的位置,去计算frame,这里我们为TOP
而这里的96如何来的呢?我们知道创建分割线的地方为 Divider.java的addDivider,里面有个信息:
/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
private void addDivider(Configuration configuration) {
Log.i(TAG, "zhoujinjian Divider addDivider()");
mView = (DividerView)
LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
//我们这里的dp2px=2,所以会是96高。
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);
mView.injectDependencies(mWindowManager, mDividerState);
}
如上,我们发现我们穿过层层阻碍,走完了分屏的创建过程的大半过程。分屏过程错复杂,我们还有个触发最近列表的过程需要讲解。
我们看到了这里,经历了dock的整个创建过程,我们再回到我们出发的起点位置,看个内容:
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);
}
}
RecentsImpl.java的dockTopTask方法,我们启动分屏的开始部分。
我们看到了,如果创建成功,我们进入里面的方法EventBus的send我们不去关注了,想要了解的,去看看EventBus的总线方式,以及如何解耦的。核心便是通过注解,系统将需要接收的通过方法的参数类型进行匹配。
我们需要看下面的:showRecents ,这个便是我们进入分屏,下方出现的最近列表界面启动的地方。此方法我们不详细扩展了,本质就是启动了一个activity即可(startRecentsActivity)。
(二)Multi-window-mode 退出流程
exit-multi-window-mode.png参考文档:
浅析Android的窗口
Android 7.0中的多窗口实现解析
google 进入分屏后在横屏模式按 home 键界面错乱 (二)
网友评论