Android(安卓)7.1.2(Android(安卓)N) Multi-window-mode--多窗口加载显示流程、退出流程
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 值为 220dp 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
@Overrideprotected 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.javapublic 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@Overridepublic 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.javapublic 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
@Overridepublic 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. */@Overridepublic 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 键界面错乱 (二)
更多相关文章
- android读书知识点总结
- Android(安卓)Listener侦听的N种写法
- xcode与eclipse整合cocos2dx
- View的事件体系《Android开发艺术探索》笔记
- Android(安卓)调用系统相机拍照并储存在本地
- android系统提供的常用命令行工具
- 关于Android中的四大组件(Activity)
- Android(安卓)多线程之HandlerThread 完全详解
- WebView Android(安卓)调用js且需要获取返回结果