美文网首页Android Debug
Android 7.1.2(Android N) Multi-w

Android 7.1.2(Android N) Multi-w

作者: izhoujinjian | 来源:发表于2017-07-28 15:52 被阅读992次

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

    相关文章

      网友评论

        本文标题:Android 7.1.2(Android N) Multi-w

        本文链接:https://www.haomeiwen.com/subject/snqflxtx.html