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 键界面错乱 (二)

更多相关文章

  1. android读书知识点总结
  2. Android(安卓)Listener侦听的N种写法
  3. xcode与eclipse整合cocos2dx
  4. View的事件体系《Android开发艺术探索》笔记
  5. Android(安卓)调用系统相机拍照并储存在本地
  6. android系统提供的常用命令行工具
  7. 关于Android中的四大组件(Activity)
  8. Android(安卓)多线程之HandlerThread 完全详解
  9. WebView Android(安卓)调用js且需要获取返回结果

随机推荐

  1. [React-Native 0.56.0]Android(安卓)Coul
  2. Android保持屏幕常亮的方法总结
  3. android 获取IP
  4. Android之如何使用junit
  5. Android如何使用selector设置RelativeLay
  6. Android的数据存储方式
  7. Android(安卓)max17044平台驱动注册
  8. Android中内容观察者的使用---- ContentO
  9. Android(安卓)Glide设置图片圆角,亲测有效
  10. Android三种实现定时器的方法